Use Barplot in Polar Coordinate with ggplot2 to Visualize Cars Engine Power

In this article, we’ll visualize the automobiles’ horsepower using an ordered barplot in a polar coordinate. Major technical highlights in this article include:


Data cleanup

In this visualization, we’ll use the mtcars dataset which is built in base R.

# packageslibrary(ggplot2)library(dplyr)library(forcats)
# set default global themetheme_set(theme_minimal(base_size = 13) + theme( axis.text.x = element_text(colour = "snow3")))

Prepare the dataset to generate an ordered barplot:

  • Turn the numeric variable cyl (cylinder number ) to a categorical variable. This way, when cyl is mapped to color, the cylinder number (4, 6, or 8) will be appropriately displayed as distinct colors on a discrete color scale (legend), instead of on a less readable continuous (gradient) color scale (color bar).

  • Reorder the barplot by horsepower (hp). Here we first rearrange the rows by hp, and turn the car variable to a factor with levels (car names) following the current row order. More details on graphic elements reordering in ggplot2 can be found in this complete guide.

mtcars2 <- mtcars %>%  as_tibble() %>%   mutate(car = rownames(mtcars), cyl = factor(cyl)) %>%   # reorder cars by horsepower  arrange(hp) %>%  mutate(car = factor(car, levels = car)) 
head(mtcars2, 4) # ready for visualization

Output:

# A tibble: 4 × 12
mpg cyl disp hp drat wt qsec vs am gear carb car
<dbl> <fct> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <fct>
1 30.4 4 75.7 52 4.93 1.62 18.5 1 1 4 2 Honda Civic
2 24.4 4 147. 62 3.69 3.19 20 1 0 4 2 Merc 240D
3 33.9 4 71.1 65 4.22 1.84 19.9 1 1 4 1 Toyota Coro…
4 32.4 4 78.7 66 4.08 2.2 19.5 1 1 4 1 Fiat 128

Visualization

Create an ordered bar plot.

p1 <- mtcars2 %>%   ggplot(aes(x = car, y = hp, fill = cyl)) +  geom_col()p1

Transform the linear coordinate to a polar coordinate.

  • theta = "x" (default) maps the x aesthetic from linear coordinate to angles in the polar coordinate. In contrast, theta = "y" maps the y aesthetic to angles, and is used to create pie and donut charts.

  • Use clip = "off" to display graphical elements that extend beyond the plot boundary (often done together with plot margin adjustment; see more in the step of creating plot p5).

p2 <- p1 + coord_polar(theta = "x", clip = "off")p2

We aim to label the car names in line with the orientation of associated bars. To do this, first we need to calculate the angles by which the texts need to be rotated.

n <- nrow(mtcars2)angle_perSlice = 360 / n
# degrees of angle to rotate per bar / textangle_perSlice

Output:

[1] 11.25

Create a vector of angles of successive rotations for car names. The rotation starts from the first bar (i.e., the most left in linear coordinate) - Honda Civic, with an initial angle of 81° (by visual estimate of p2).

angles_to_rotate <- 81 - (0:(n-1)) * angle_perSliceangles_to_rotate

Output:

[1] 81.00 69.75 58.50 47.25 36.00 24.75 13.50 2.25 -9.00
[10] -20.25 -31.50 -42.75 -54.00 -65.25 -76.50 -87.75 -99.00 -110.25
[19] -121.50 -132.75 -144.00 -155.25 -166.50 -177.75 -189.00 -200.25 -211.50
[28] -222.75 -234.00 -245.25 -256.50 -267.75

Add labels of car names. The x-axis from a linear coordinate collapses into the central point in a polar coordinate. Accordingly, mapped with x = car (inherited from the ggplot line), all texts are headed towards the center of the circle. In addition, the texts are left aligned (by hjust = 0) to the y position that is 40 units above the height of the lollipop (y = hp + 40), with respective rotations specified in angles_to_rotate.

p2 + geom_text(aes(label = car, y = hp + 10),                angle = angles_to_rotate,               hjust = 0)

In the above plot, texts on the right half are nicely rotated, but those on the left half are turned upside down, and are difficult to read. The following edit aims to readjust the rotation angles of texts on the left side.

# same text angles for the first 17 cars (right half) from Honda civic to Merc 280Cangles1 <- angles_to_rotate[1:17] 
# update text angles for the left half cars from Dodge to Maseratiangles2 <- 71 - (0: (n - 17 -1)) * angle_perSlice
# an updated angle vectorangles_to_rotate_new <- c(angles1, angles2)
p2 + geom_text(aes(label = car, y = hp + 10), angle = angles_to_rotate_new, hjust = 0)

Now the text rotation angle are well adjusted. However, as all texts are left aligned to y = hp + 10, the left-half texts are shifted towards the right, and mostly overlapped with the bars. To fix this, we’ll instead right-justify texts on the left side, while keep the left justification for texts on the right side.

# trial 3: fix the alignmentp2 + geom_text(aes(label = car, y = hp + 10),                angle = angles_to_rotate_new,               hjust = c(rep(0, 17), rep(1, n - 17) ))

Much better - now add a few more touch on the texts font and size. In particular, we’ll use smaller font sizes of 2 ~ 2.5 for the first 5 cars (of smaller horsepower) that occupy a more jammed space, and a larger size 3 for the other texts. Note that the last (largest) bar’s text label “Maserati Bora” is not displayed completely. We’ll fix it at the last step.

p3 <- p2 +   geom_text(aes(label = car, y = hp + 10),             angle = angles_to_rotate_new,            hjust = c(rep(0, 17), rep(1, n - 17) ),            size = c(2, 2, 2.2, 2.3, 2.5, rep(3, n - 5)),             fontface = "bold")p3

Add plot title at the circle center, and update color scale.

p4 <- p3 +   annotate(geom = "text", x = 0, y = 0,             label = "Turbine \nPower", size = 4,            fontface = "bold", color = "white") +  scale_fill_manual(values = c("snow4", "steelblue", "brown"))p4

A touch on the theme:

  • Apply the theme of void to make a clean background.
  • Relocate the legend to the top right corner (using relative coordinates of a linear coordinate).
  • Increase the margin at the top of the plot which, together with the prior clip = "off", displays completely the “Maserati” text label.
p5 <- p4 + theme_void() +  theme(legend.position = c(.7, .93),        plot.margin = margin(t = 30, unit = "pt")) p5
# packageslibrary(ggplot2)library(dplyr)library(forcats)
# set default global themetheme_set(theme_minimal(base_size = 13) + theme( axis.text.x = element_text(colour = "snow3")))
# data cleanupmtcars2 <- mtcars %>% as_tibble() %>% mutate(car = rownames(mtcars), cyl = factor(cyl)) %>% # reorder cars by horsepower arrange(hp) %>% mutate(car = factor(car, levels = car))
head(mtcars2, 4) # ready for visualization

# Create an ordered bar plot.p1 <- mtcars2 %>% ggplot(aes(x = car, y = hp, fill = cyl)) + geom_col()p1

# Transform the linear coordinate to a polar coordinate.p2 <- p1 + coord_polar(clip = "off")p2

# manually label car names in line with the orientation of associated bars# first calculate the angles by which the texts need to be rotated. n <- nrow(mtcars2)angle_perSlice = 360 / n
# degrees of angle to rotate per bar / textangle_perSlice
# Create a vector of angles of successive rotations for all car names.angles_to_rotate <- 81 - (0:(n-1)) * angle_perSliceangles_to_rotate
# Add labels of car names.p2 + geom_text(aes(label = car, y = hp + 10), angle = angles_to_rotate, hjust = 0)

# Readjust the rotation angles of texts on the left side. # keep the same text angles for the first 17 cars (right half) from Honda civic to Merc 280Cangles1 <- angles_to_rotate[1:17] # update text angles for the left half cars from Dodge to Maseratiangles2 <- 71 - (0: (n - 17 -1)) * angle_perSlice# an updated angle vectorangles_to_rotate_new <- c(angles1, angles2)
p2 + geom_text(aes(label = car, y = hp + 10), angle = angles_to_rotate_new, hjust = 0)

# Update text justification:## Right-justify texts instead on the left side of plot;## and keep the same left justification for texts on the right side. p2 + geom_text(aes(label = car, y = hp + 10), angle = angles_to_rotate_new, hjust = c(rep(0, 17), rep(1, n - 17) ))

# Update the labels size and font. # use smaller font for the first 5 cars that occupy a very small spacep3 <- p2 + geom_text(aes(label = car, y = hp + 10), angle = angles_to_rotate_new, hjust = c(rep(0, 17), rep(1, n - 17) ), size = c(2, 2, 2.2, 2.3, 2.5, rep(3, n - 5)), fontface = "bold")p3

# Add plot title at the circle center, and update color scale. p4 <- p3 + annotate(geom = "text", x = 0, y = 0, label = "Turbine \nPower", size = 4, fontface = "bold", color = "white") + scale_fill_manual(values = c("snow4", "steelblue", "brown"))p4

# Relocate the legend to the top right corner. # Increase the margin at the top of the plotp5 <- p4 + theme_void() + theme(legend.position = c(.7, .93), plot.margin = margin(t = 30, unit = "pt")) p5




Continue Exploring — 🚀 one level up!


In the following article, we’ll update the above barplot into a donut-lollipop with color and font-enriched plot title.



All the plots above are generated by converting the x aesthetic from a linear coordinate into the angles within a polar coordinate. More common in practice, however, the y aesthetic is mapped into angles to make “authentic” pie and donut charts to visualize the “proportion of a whole”. Check the following exploded donut plots in faceted panels that visualize the contribution of the top 4 largest GDP makers to the total GDP in each continent.