Search code examples
rggplot2geometrybubble-chart

Bubble Chart with bubbles aligned along their bottom edges


Is there a simple way to make a bubble chart in R like this:

enter image description here

I've played around with ggplot with fake data and have gotten this far:

cat<-c("A", "A", "B", "B", "C", "C")
chara<-c("1", "0", "1", "0", "1", "0")
percent<-c(80, 20, 60, 40, 90,10)
xcoord<-c(10,10,11,11,12,12)
ycoord<-c(10,10,10,10,10,10)

DF<-data.frame(cat,chara, percent, xcoord, ycoord)

NewBubbleChart <- ggplot(DF, aes(x = cat, y = "", size = percent, label = NULL, fill = chara), legend = FALSE) +
                    geom_point(color = "grey50", shape = 21, alpha = 0.99) +  
                   #geom_text(size=4) +
                    theme_bw() +
                    scale_size(range = c(5, 20))

NewBubbleChart <- NewBubbleChart +
                    scale_fill_manual(name = "Type",
                                      values = c("darkblue", "lightblue"),
                                      labels = c("0" = "Type 0", "1" = "Type 1"))

I ended up not using the xcoord and ycoord part, but I left it in. I know that a bar chart would work too, but a bubble chart is wanted instead.


Solution

  • This seems to come pretty close.

    library(ggplot2)
    # function to calculate coords of a circle
    circle <- function(center,radius) {
      th <- seq(0,2*pi,len=200)
      data.frame(x=center[1]+radius*cos(th),y=center[2]+radius*sin(th))
    }
    # example dataset, similar to graphic
    df <- data.frame(bank=paste("Bank",LETTERS[1:5]),start=1000*(5:1),end=500*(5:1))    
    max <- max(df$start)
    n.bubbles <- nrow(df)
    scale <- 0.4/sum(sqrt(df$start))
    # calculate scaled centers and radii of bubbles
    radii <- scale*sqrt(df$start)
    ctr.x <- cumsum(c(radii[1],head(radii,-1)+tail(radii,-1)+.01))
    # starting (larger) bubbles
    gg.1  <- do.call(rbind,lapply(1:n.bubbles,function(i)cbind(group=i,circle(c(ctr.x[i],radii[i]),radii[i]))))
    text.1 <- data.frame(x=ctr.x,y=-0.05,label=paste(df$bank,df$start,sep="\n"))
    # ending (smaller) bubbles
    radii <- scale*sqrt(df$end)
    gg.2  <- do.call(rbind,lapply(1:n.bubbles,function(i)cbind(group=i,circle(c(ctr.x[i],radii[i]),radii[i]))))
    text.2 <- data.frame(x=ctr.x,y=2*radii+0.02,label=df$end)
    # make the plot
    ggplot()+
      geom_polygon(data=gg.1,aes(x,y,group=group),fill="dodgerblue")+
      geom_path(data=gg.1,aes(x,y,group=group),color="grey50")+
      geom_text(data=text.1,aes(x,y,label=label))+
      geom_polygon(data=gg.2,aes(x,y,group=group),fill="green2")+
      geom_path(data=gg.2,aes(x,y,group=group),color="grey50")+
      geom_text(data=text.2,aes(x,y,label=label), color="white")+
      labs(x="",y="")+scale_y_continuous(limits=c(-0.1,2.5*scale*sqrt(max(df$start))))+
      coord_fixed()+
      theme(axis.text=element_blank(),axis.ticks=element_blank(),panel.grid=element_blank())
    

    So this is a "bubble-in-bubble" chart, which represents the change in a metric (bank market capitalization in your graphic) between two events or times (before and after the economic collapse, in your graphic). In order for this to work the ending condition must be smaller than the starting condition (otherwise the "inner" bubble is larger than the outer bubble).

    The trick bit is getting the circles to be aligned along their bottom edges. This is really difficult using geom_point(...), so I chose to just draw circles for the bubbles instead.

    I suspect you'll have to tweak the positioning of the text a bit by hand in a real case. If you want multiple rows (as in the graphic), you might consider ggplot facets.

    Finally, if you want the circles shaded (e.g. with a color gradient) this is not really what ggplot is intended for: it's possible but IMO much more work than it's worth.