Is there a way to draw an arrow between two pie charts using coordinates from the outer circle of the two pie charts as start and end position? My arrow is drawn by trying with different x's and y's.
#pie chart 1
pie1 <- count(diamonds, cut) %>%
ggplot() +
geom_bar(aes(x = '', y = n, fill = cut), stat = 'identity', width = 1) +
coord_polar('y', start = 0) +
theme_void()+
theme(legend.position = 'none')
#pie chart 2
pie2 <- count(diamonds, color) %>%
ggplot() +
geom_bar(aes(x = '', y = n, fill = color), stat = 'identity', width = 1) +
coord_polar('y', start = 0) +
theme_void()+
theme(legend.position = 'none')
# Plots and arrow combined
grid.newpage()
vp_fig <- viewport() # top plot area
pushViewport(vp_fig)
grid.draw(rectGrob())
vp_pie1 <- viewport(x =.5, y= 1, width = .25, height = .25, just = c('centre', 'top')) #viewport for pie chart 1
pushViewport(vp_pie1)
grid.draw(ggplotGrob(pie1))
popViewport()
vp_pie2 <- viewport(x =.25, y= .5, width = .25, height = .25, just = c('left', 'centre')) #viewport for pie chart 2
pushViewport(vp_pie2)
grid.draw(ggplotGrob(pie2))
popViewport()
upViewport() #move to top plot area
grid.lines(x = c(.45, .37), y = c(.8, .61), arrow = arrow()) # arrow between the pie charts
Here's a possible approach.:
Step 0. Create pie charts, & convert them to a list of grobs:
pie1 <- count(diamonds, fill = cut) %>%
ggplot() +
geom_col(aes(x = '', y = n, fill = fill), width = 1) +
coord_polar('y', start = 0) +
theme_void()+
theme(legend.position = 'none')
pie2 <- pie1 %+% count(diamonds, fill = color)
pie3 <- pie1 %+% count(diamonds, fill = clarity)
pie.list <- list(pie1 = ggplotGrob(pie1),
pie2 = ggplotGrob(pie2),
pie3 = ggplotGrob(pie3))
rm(pie1, pie2, pie3)
Step 1. Define centre coordinates / radius for each pie:
pie.coords <- data.frame(
pie = names(pie.list),
center.x = c(0, 3, 5),
center.y = c(0, 4, 2),
radius = c(1, 1.5, 0.5)
)
Step 2. Calculate the appropriate start & end arrow coordinates for each combination of pies, taking into account each pie's size (assuming each pie can have a different radius value):
arrow.coords <- expand.grid(start = pie.coords$pie,
end = pie.coords$pie,
KEEP.OUT.ATTRS = FALSE,
stringsAsFactors = FALSE) %>%
filter(start != end) %>%
left_join(pie.coords, by = c("start" = "pie")) %>%
left_join(pie.coords, by = c("end" = "pie"))
colnames(arrow.coords) <- colnames(arrow.coords) %>%
gsub(".x$", ".start", .) %>%
gsub(".y$", ".end", .)
arrow.coords <- arrow.coords %>%
mutate(delta.x = center.x.end - center.x.start,
delta.y = center.y.end - center.y.start,
distance = sqrt(delta.x^2 + delta.y^2)) %>%
mutate(start.x = center.x.start + radius.start / distance * delta.x,
start.y = center.y.start + radius.start / distance * delta.y,
end.x = center.x.end - radius.end / distance * delta.x,
end.y = center.y.end - radius.end / distance * delta.y) %>%
select(starts_with("start"),
starts_with("end")) %>%
mutate_at(vars(start, end), factor)
Step 3. Convert pie center / radius into x & y min/max coordinates:
pie.coords <- pie.coords %>%
mutate(xmin = center.x - radius,
xmax = center.x + radius,
ymin = center.y - radius,
ymax = center.y + radius)
Step 4. Define function to create an annotation_custom()
layer for each pie (this is optional; I just don't want to type the same thing repeatedly for each pie):
annotation_custom_list <- function(pie.names){
result <- vector("list", length(pie.names) + 1)
for(i in seq_along(pie.names)){
pie <- pie.names[i]
result[[i]] <- annotation_custom(
grob = pie.list[[pie]],
xmin = pie.coords$xmin[pie.coords$pie == pie],
xmax = pie.coords$xmax[pie.coords$pie == pie],
ymin = pie.coords$ymin[pie.coords$pie == pie],
ymax = pie.coords$ymax[pie.coords$pie == pie])
}
# add a blank geom layer to ensure the resulting ggplot's
# scales extend sufficiently to show each pie
result[[length(result)]] <- geom_blank(
data = pie.coords %>% filter(pie %in% pie.names),
aes(xmin = xmin, ymin = ymin, xmax = xmax, ymax = ymax)
)
return(result)
}
Step 5. Putting it all together:
ggplot() +
# plot pie grobs
annotation_custom_list(c("pie1", "pie2", "pie3")) +
# plot arrows between grobs
# (adjust the filter criteria to only plot between specific pies)
geom_segment(data = arrow.coords %>%
filter(as.integer(start) < as.integer(end)),
aes(x = start.x, y = start.y,
xend = end.x, yend = end.y),
arrow = arrow()) +
# theme_void for clean look
theme_void()