# Install packages
if (!requireNamespace("tidyverse", quietly = TRUE)) {
install.packages("tidyverse")
}
# Load packages
library(tidyverse)Circular Barplot
Circular Barplot is a variation of the well-known bar chart where bars are displayed along a circle instead of a straight line. Note that while visually appealing, circular bar charts must be used with caution because the groups do not share the same Y-axis. However, they are well-suited for periodic data.
Example

Setup
System Requirements: Cross-platform (Linux/MacOS/Windows)
Programming Language: R
Dependencies:
tidyverse
Data Preparation
Using Rβs built-in iris dataset, a custom dataset, and the TCGA database.
# 1.R's built-in dataβiris
head(iris) Sepal.Length Sepal.Width Petal.Length Petal.Width Species
1 5.1 3.5 1.4 0.2 setosa
2 4.9 3.0 1.4 0.2 setosa
3 4.7 3.2 1.3 0.2 setosa
4 4.6 3.1 1.5 0.2 setosa
5 5.0 3.6 1.4 0.2 setosa
6 5.4 3.9 1.7 0.4 setosa
# 2.Self-built dataset
data_customize <- data.frame(
individual=paste( "Mister ", seq(1,60), sep=""),
group=c( rep('A', 10), rep('B', 30), rep('C', 14), rep('D', 6)) ,
value=sample( seq(10,100), 60, replace=T)
)
# 3.TCGA database (gene expression data for liver cancer)
tcga_circle <- readr::read_csv(
"https://bizard-1301043367.cos.ap-guangzhou.myqcloud.com/tcga_circle.csv")Visualization
1. Basic Plotting
1.1 iris dataset
This method is the same as creating a classic bar chart. Finally, we call coord_polar() to make the chart circular. Note that the ylim() parameter is very important. If it starts from 0, the bars will start from the center. If you provide a negative value, a white circular space will appear!
iris_id <- iris[order(iris$Species),]
iris_id$new_column <- 1:nrow(iris_id)
p <- ggplot(iris_id, aes(x = new_column, y = Sepal.Length, fill = Species)) +
geom_bar(stat = "identity", width = 1) +
coord_polar(start = 0) +
theme_void() +
labs(fill = "Species", y = "Sepal.Length", x = NULL) +
theme(legend.title = element_blank())
p
This circular bar chart describes the quantitative relationships of the Sepal.Length variable among different species.
1.2 TCGA dataset
(1) Add labels
# Get the name and y-position of each label
label_data <- tcga_circle
# Calculate the angle of the label
number_of_bar <- nrow(label_data)
angle <- 90 - 360 * (label_data$id-0.5) /number_of_bar
# Calculate the label alignment: right or left?
label_data$hjust <- ifelse(angle < -90, 1, 0)
# Adjust the flip angle BY to be readable
label_data$angle <- ifelse(angle < -90, angle+180, angle)
# Plot
tcga_circle_id <- tcga_circle
tcga_circle_id$id <- as.factor(tcga_circle_id$id)
p <- ggplot(tcga_circle_id, aes(x = id, y = tcga_circle)) +
geom_bar(stat = "identity", fill=alpha("skyblue", 0.7)) +
ylim(-10,20) +
coord_polar(start = 0) +
theme_minimal() +
theme(
axis.text = element_blank (),
axis.title = element_blank(),
panel.grid = element_blank (),
plot.margin = unit(rep(-1,4), "cm") )+
labs(fill = "RowNames", y = "tcga_circle", x = NULL) +
theme(legend.title = element_blank()) +
geom_text(data=label_data,
aes(x=id, y=tcga_circle+10, label=RowNames, hjust=hjust),
color="black", fontface="bold",alpha=0.6, size=2.5,
angle= label_data$angle, inherit.aes = FALSE )
p
This circular bar chart describes the expression values of different genes in a sample of liver cancer.
(2) Add a gap
# Add a gap
empty_bar <- 20
to_add <- matrix(NA, empty_bar, ncol(tcga_circle))
colnames(to_add) <- colnames(tcga_circle)
tcga_circle_id <- rbind(tcga_circle, to_add)
tcga_circle_id$id <- seq(1, nrow(tcga_circle_id))
# Get the name and y-position of each label
label_data <- tcga_circle_id
# Calculate the angle of the label
number_of_bar <- nrow(label_data)
angle <- 90 - 360 * (label_data$id-0.5) /number_of_bar
# Calculate the label alignment: right or left?
label_data$hjust <- ifelse( angle < -90, 1, 0)
# Adjust the flip angle BY to be readable
label_data$angle <- ifelse(angle < -90, angle+180, angle)
p <- ggplot(tcga_circle_id, aes(x = id, y = tcga_circle)) +
geom_bar(stat = "identity", fill=alpha("skyblue", 0.7)) +
ylim(-10,20) +
coord_polar(start = 0) +
theme_minimal() +
theme(
axis.text = element_blank (),
axis.title = element_blank(),
panel.grid = element_blank (),
plot.margin = unit(rep(-1,4), "cm") )+
labs(fill = "RowNames", y = "tcga_circle", x = NULL) +
theme(legend.title = element_blank()) +
geom_text(data=label_data,
aes(x=id, y=tcga_circle+10, label=RowNames, hjust=hjust),
color="black", fontface="bold",alpha=0.6, size=2.5,
angle= label_data$angle, inherit.aes = FALSE )
p
This circular bar chart describes the expression values of different genes in a sample of liver cancer.
2. Grouping
2.1 Add gaps between groups
Taking iris data as an example
iris_id <- iris[order(iris$Species),]
iris_id$new_column <- 1:nrow(iris_id)
# Add gaps
empty_bar <- 4
to_add <- data.frame( matrix(NA, empty_bar*nlevels(iris_id$Species), ncol(iris_id)) )
colnames(to_add) <- colnames(iris_id)
to_add$Species <- rep(levels(iris_id$Species), each=empty_bar)
iris_id <- rbind(iris_id, to_add)
iris_id <- iris_id %>% arrange(Species)
iris_id$new_column <- seq(1, nrow(iris_id))
# Plot
iris_id$new_column<-as.factor(iris_id$new_column)
p <- ggplot(iris_id, aes(x = new_column, y = Sepal.Length,fill = Species)) +
geom_bar(stat = "identity") +
ylim(-20,10) +
coord_polar(start = 0) +
theme_minimal() +
theme(
axis.text = element_blank (),
axis.title = element_blank(),
panel.grid = element_blank (),
plot.margin = unit(rep(-1,4), "cm") )+
labs(fill = "Species", y = "Sepal.Length", x = NULL) +
theme(legend.title = element_blank())
p
This circular bar chart describes the quantitative relationships of the Sepal.Length variable among different species.
2.2 Arranged by group size
Taking self-built data as an example
# Create gaps between each group
empty_bar <- 4
to_add <- data.frame( matrix(NA, empty_bar*nlevels(data_customize$group), ncol(data_customize)) )
colnames(to_add) <- colnames(data_customize)
to_add$group <- rep(levels(data_customize$group), each=empty_bar)
data_customize_id <- rbind(data_customize, to_add)
data_customize_id <- data_customize_id %>% arrange(group,value)
data_customize_id$id <- seq(1, nrow(data_customize_id))
# Add labels
label_data2 <- data_customize_id
number_of_bar <- nrow(label_data2)
angle <- 90 - 360 * (label_data2$id-0.5) /number_of_bar
label_data2$hjust <- ifelse( angle < -90, 1, 0)
label_data2$angle <- ifelse(angle < -90, angle+180, angle)
# Plot
data_customize_id$id <- as.factor(data_customize_id$id)
p <- ggplot(data_customize_id, aes(x=id, y=value, fill=group)) +
geom_bar(stat="identity", alpha=0.5) +
ylim(-100,120) +
theme_minimal() +
theme(
legend.position = "none",
axis.text = element_blank(),
axis.title = element_blank(),
panel.grid = element_blank(),
plot.margin = unit(rep(-1,4), "cm")
) +
coord_polar() +
geom_text(data=label_data2,
aes(x=id, y=value+10, label=individual, hjust=hjust),
color="black", fontface="bold",alpha=0.6, size=2.5,
angle= label_data2$angle, inherit.aes = FALSE )
p
This circular bar plot describes the numerical value of different IDs.
3. Beautify plot
# Set the number of "empty columns" to add at the end of each group.
data_customize_id <- data_customize
empty_bar <- 3
to_add <- data.frame(matrix(NA, empty_bar*nlevels(data_customize$group),
ncol(data_customize)))
colnames(to_add) <- colnames(data_customize)
to_add$group <- rep(levels(data_customize_id$group), each=empty_bar)
data_customize_id <- rbind(data_customize, to_add)
data_customize_id <- data_customize_id %>% arrange(group)
data_customize_id$id <- seq(1, nrow(data_customize_id))
# Get the name and y position of each label
label_data <- data_customize_id
number_of_bar <- nrow(label_data)
angle <- 90 - 360 * (label_data2$id-0.5) /number_of_bar
label_data$hjust <- ifelse( angle < -90, 1, 0)
label_data$angle <- ifelse(angle < -90, angle+180, angle)
# Prepare the baseline data frame
base_data <- data_customize_id %>%
group_by(group) %>%
summarize(start=min(id), end=max(id) - empty_bar) %>%
rowwise() %>%
mutate(title=mean(c(start, end)))
# Prepare grid data frame (scale)
grid_data <- base_data
grid_data$end <- grid_data$end[ c( nrow(grid_data), 1:nrow(grid_data)-1)] + 1
grid_data$start <- grid_data$start - 1
grid_data <- grid_data[-1,]
# Plot
p <- ggplot(data_customize_id, aes(x=as.factor(id), y=value, fill=group)) +
geom_bar(aes(x=as.factor(id), y=value, fill=group), stat="identity", alpha=0.5) +
geom_segment(data=grid_data, aes(x = end, y = 80, xend = start, yend = 80), colour = "grey", alpha=1, size=0.3 , inherit.aes = FALSE ) +
geom_segment(data=grid_data, aes(x = end, y = 60, xend = start, yend = 60), colour = "grey", alpha=1, size=0.3 , inherit.aes = FALSE ) +
geom_segment(data=grid_data, aes(x = end, y = 40, xend = start, yend = 40), colour = "grey", alpha=1, size=0.3 , inherit.aes = FALSE ) +
geom_segment(data=grid_data, aes(x = end, y = 20, xend = start, yend = 20), colour = "grey", alpha=1, size=0.3 , inherit.aes = FALSE ) +
annotate("text", x = rep(max(data$id),4), y = c(20, 40, 60, 80), label = c("20", "40", "60", "80") , color="grey", size=3 , angle=0, fontface="bold", hjust=1) +
geom_bar(aes(x=as.factor(id), y=value, fill=group), stat="identity", alpha=0.5) +
ylim(-100,120) +
theme_minimal() +
theme(
legend.position = "none",
axis.text = element_blank(),
axis.title = element_blank(),
panel.grid = element_blank(),
plot.margin = unit(rep(-1,4), "cm")
) +
coord_polar() +
geom_text(data=label_data, aes(x=id, y=value+10, label=individual, hjust=hjust), color="black", fontface="bold",alpha=0.6, size=2.5, angle= label_data$angle, inherit.aes = FALSE ) +
# Add baseline information
geom_segment(data=base_data, aes(x = start, y = -5, xend = end, yend = -5), colour = "black", alpha=0.8, size=0.6 , inherit.aes = FALSE ) +
geom_text(data=base_data, aes(x = title, y = -18, label=group), hjust=c(1,1,0,0), colour = "black", alpha=0.8, size=4, fontface="bold", inherit.aes = FALSE)
p
This circular bar chart describes the numerical value of different IDs.
Applications
This circular bar chart, based on family whole-genome data from Corpas et al., visualizes all genetic variations on human chromosome 21. [1]
Reference
[1] Parveen A, Khurana S, Kumar A. Overview of Genomic Tools for Circular Visualization in the Next-generation Genomic Sequencing Era. Curr Genomics. 2019 Feb;20(2):90-99.
