Making a circular barplot with a hollow center (aka race track plot)

前端 未结 4 791
滥情空心
滥情空心 2021-01-30 13:22

I was asked to recreate the following style of plot. (Please ignore the question of whether this is a good type of visualization and charitably consider this as adding a colorf

相关标签:
4条回答
  • 2021-01-30 14:02

    Since the plot is circular, it can be easily done by circlize package.

    First the data:

    Category <- c("Electronics", "Appliances", "Books", "Music", "Clothing", 
            "Cars", "Food/Beverages", "Personal Hygiene", 
            "Personal Health/OTC", "Hair Care")
    Percent <- c(81, 77, 70, 69, 69, 68, 62, 62, 61, 60)
    color = rainbow(length(Percent))
    

    Reverse the three vectors since circlize adds each element from inside to outside by default:

    Category = rev(Category)
    Percent = rev(Percent)
    color = rev(color)
    

    If you image the circle is a bent plot region, then it is just adding rectangles, lines and texts.

    library(circlize)
    
    par(mar = c(1, 1, 1, 1))
    circos.par("start.degree" = 90)
    circos.initialize("a", xlim = c(0, 100)) # 'a` just means there is one sector
    circos.trackPlotRegion(ylim = c(0.5, length(Percent)+0.5), track.height = 0.8, 
        bg.border = NA, panel.fun = function(x, y) {
        xlim = get.cell.meta.data("xlim") # in fact, it is c(0, 100)
        for(i in seq_along(Percent)) {
            circos.lines(xlim, c(i, i), col = "#CCCCCC")
            circos.rect(0, i - 0.45, Percent[i], i + 0.45, col = color[i], 
                border = "white")
            circos.text(xlim[2], i, paste0(Category[i], " - ", Percent[i], "%"), 
                adj = c(1, 0.5)) 
        }
    })
    circos.clear()
    
    text(0, 0, "GLOBAL", col = "#CCCCCC")
    

    enter image description here

    0 讨论(0)
  • 2021-01-30 14:03

    Here's a non-ggplot2 (base R graphics) solution using the plotrix package, which contains two nice functions: draw.circle() and draw.arc():

    circBarPlot <- function(x, labels, colors=rainbow(length(x)), cex.lab=1) {
      require(plotrix)
      plot(0,xlim=c(-1.1,1.1),ylim=c(-1.1,1.1),type="n",axes=F, xlab=NA, ylab=NA)
      radii <- seq(1, 0.3, length.out=length(x))
      draw.circle(0,0,radii,border="lightgrey")
      angles <- (1/4 - x)*2*pi
      draw.arc(0, 0, radii, angles, pi/2, col=colors, lwd=130/length(x), lend=2, n=100)
      ymult <- (par("usr")[4]-par("usr")[3])/(par("usr")[2]-par("usr")[1])*par("pin")[1]/par("pin")[2]
      text(x=-0.02, y=radii*ymult, labels=paste(labels," - ", x*100, "%", sep=""), pos=2, cex=cex.lab)
    }
    
    circBarPlot(Percent/100, Category)
    text(0,0,"GLOBAL",cex=1.5,col="grey")
    

    It gives me:

    Circular bar plot

    0 讨论(0)
  • 2021-01-30 14:08

    Another base solution that doesn't rely on plotrix package:

    circular.barplot<-function(values, labels, col, cex){
        df<-data.frame(values=sort(values), labels=labels[order(values)])
        col<-col[order(values)]
        plot(NA,xlim=c(-1.3,1.3),ylim=c(-1.3,1.3),axes=F, xlab=NA, ylab=NA, asp=1)
        t<-sapply(df$values,function(x).5*pi-seq(0, 2*pi*x/100,length=1000))
        x<-sapply(1:nrow(df),function(x)(.3+x/nrow(df))*cos(t[,x]))
        y<-sapply(1:nrow(df),function(x)(.3+x/nrow(df))*sin(t[,x]))
        for(i in 1:nrow(df)){
            lines(x=x[,i],y=y[,i],col=col[i],lwd=10,lend=1)
            text(x[1,i],y[1,i],paste(df$labels[i]," - ",df$values[i],"%",sep=""),
                 pos=2,cex=cex)
            }
        }
    

    enter image description here

    0 讨论(0)
  • 2021-01-30 14:10

    I think an immediate fix is to create some "empty" entries. I'd create internetImportance data.frame like this:

    Category <- c("Electronics", "Appliances", "Books", "Music", "Clothing", 
            "Cars", "Food/Beverages", "Personal Hygiene", 
            "Personal Health/OTC", "Hair Care")
    Percent <- c(81, 77, 70, 69, 69, 68, 62, 62, 61, 60)
    
    internetImportance <- data.frame(Category,Percent)
    
    len <- 4
    df2 <- data.frame(Category = letters[1:len], Percent = rep(0, len), 
                                     Category2 = rep("", len))
    internetImportance$Category2 <- 
     paste0(internetImportance$Category," - ",internetImportance$Percent,"%")
    
    # append number to category name
    internetImportance <- rbind(internetImportance, df2)
    
    # set factor so it will plot in descending order 
    internetImportance$Category <-
        factor(internetImportance$Category, 
        levels=rev(internetImportance$Category))
    

    And then I'd plot ggplot2 with fill=category2 as follows:

    ggplot(internetImportance, aes(x = Category, y = Percent,
        fill = Category2)) + 
        geom_bar(width = 0.9, stat="identity") + 
        coord_polar(theta = "y") +
        xlab("") + ylab("") +
        ylim(c(0,100)) +
        ggtitle("Top Product Categories Influenced by Internet") +
        geom_text(data = internetImportance, hjust = 1, size = 3,
                  aes(x = Category, y = 0, label = Category2)) +
        theme_minimal() +
        theme(legend.position = "none",
              panel.grid.major = element_blank(),
              panel.grid.minor = element_blank(),
              axis.line = element_blank(),
              axis.text.y = element_blank(),
              axis.text.x = element_blank(),
              axis.ticks = element_blank())
    

    This gives me:

    enter image description here

    You can add a geom_text(label="GLOBAL", x=.5, y=.5, size=4) + before theme_minimal to add the text GLOBAL.

    0 讨论(0)
提交回复
热议问题