Use Donut-Lollipops with ggplot2 to Visualize Cars Horsepower
In the earlier article, we visualized the cars’ engine horsepower with a reordered barplot in a polar coordinate. In this work, we’ll update the bars into lollipops. These wheels spoke-like geometrics may further jazz the graphic up. Compared with the prior work, new technical highlights include:
In this visualization, we’ll use the mtcars dataset which is built in base R.
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, instead of on a less readable continuous (gradient) color scale.
Rearrange the rows (car names) by order of horsepower (hp) to reorder the lollipops. More details about reordering of graphical elements in ggplot2 can be found in this complete guide.
# packageslibrary(ggplot2)library(dplyr)library(forcats) # set default global themetheme_set(theme_minimal(base_size =13) +theme(axis.text.x =element_text(colour ="snow3"))) mtcars2 <- mtcars %>%as_tibble() %>%mutate(car =rownames(mtcars), cyl =factor(cyl)) %>%# reorder cars by horsepowerarrange(hp) %>%mutate(car =factor(car, levels = car)) head(mtcars2, 4) # ready for visualization
Transform the linear coordinate to a polar coordinate.
theta = "x" (default) maps the x aesthetic 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 lollipops. 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 lollipop / textangle_perSlice
Output:
[1] 11.25
Create a vector of angles of successive rotations for car names. The rotation starts from the first lollipop (i.e., the most left in linear coordinate) - Honda Civic, with an initial angle of 81° (by visual estimate of p2).
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 +40), angle = angles_to_rotate,hjust =0)
In the above plot, texts on the right half look 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 +40), 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 + 40, the left-side texts are shifted to the right, and mostly overlapped with the lollipops. 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 +40), 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. Note that the last (largest) lollipop’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 +40), angle = angles_to_rotate_new,hjust =c(rep(0, 17), rep(1, n -17) ),size =3, fontface ="bold")p3
Add plot title at the center of the graphic, here with three major technical highlights:
# load google fontslibrary(showtext)showtext_auto()font_add_google("Anton", "Anton")
(2)Format the text using ggtext package. It invovles two steps:
Incorporate HTML in the title string. The simple line breaks in the script (made by pressing Enter or return key) are invisible in the rendered graphics. Instead, use tag <br> to make indicated line breaks in the rendered graphic.
To render the HTML markup, inside the annotate function, replace geom = "text" with geom = "richtext". (Alternatively, as in this example, if the text can be customized using the theme syntax, you can replace element_text() with element_markdown(), or replace with element_textbox_simple() for automatic text wrapping)
# format the title library(ggtext) mytitle <-c('**<span style="font-family:Anton;">AUTOMABILES<br> HORSEPOWER<br></span> with <span style="color:orange;font-family:Anton;">4</span>, <span style="color:steelblue;font-family:Anton;">**6**</span>, <span style="color:red2;font-family:Anton;">8</span><br> cylinders**')
(3)Create a donut hole: as the text y value is negative (e.g., -300) in linear coordinate, it correspondingly generates a central donut hole in the polar coordinate.
p4 <- p3 +annotate(geom ="richtext",label = mytitle,x =0, y =-300, hjust = .48, size =4, # family = "Anton",label.color =NA, fill =NA) +# udpate the color scale to be consistent with title colorsscale_color_manual(values =c("orange", "steelblue", "red2")) p4
A final touch on the theme:
Apply the theme of void to make a clean background.
Increase the margin at the top and bottom of the plot. Together with the prior clip = "off", this displays completely the text “Maserati” at the top of the plot.
The horizontal hline at y = 0 from a linear coordinate is transformed into a circle at the base of lollipops in the polar coordinate.
p5 <- p4 +theme_void() +theme(legend.position ="none",plot.margin =margin(t =30, b =10, unit ="pt")) +geom_hline(yintercept =0)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"))) mtcars2 <- mtcars %>%as_tibble() %>%mutate(car =rownames(mtcars), cyl =factor(cyl)) %>%# reorder cars by horsepowerarrange(hp) %>%mutate(car =factor(car, levels = car)) head(mtcars2, 4) # ready for visualization ### Visualization ---- ---- ---- ---- ---- ---- ---- # Create an ordered lollipop plot.p1 <- mtcars2 %>%ggplot(aes(x = car, y = hp, color = cyl)) +geom_point(size =4) +geom_segment(aes(xend = car, yend =0), linewidth =1)p1 # Transform the linear coordinate to a polar coordinate.p2 <- p1 +coord_polar(theta ="x", clip ="off")p2 # Label the car names in line with the lollipops. # First calculate angles by which the texts need to be rotated.n <-nrow(mtcars2)angle_perSlice =360/ n # degrees of angle to rotate per lollipop / 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 +40), angle = angles_to_rotate,hjust =0) # Adjust the rotation angles of texts on the left side.## keep the same text angles for the first 17 cars angles1 <- 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 +40), angle = angles_to_rotate_new,hjust =0) # Right-justify texts on the left side# while retaining left justification for texts on the right side.p2 +geom_text(aes(label = car, y = hp +40), angle = angles_to_rotate_new,hjust =c(rep(0, 17), rep(1, n -17) )) # A few more touch on the texts font and sizep3 <- p2 +geom_text(aes(label = car, y = hp +40), angle = angles_to_rotate_new,hjust =c(rep(0, 17), rep(1, n -17) ),size =3, fontface ="bold")p3 # Add plot title at the center of the graphic ## Load fonts from the Google Font Repositorylibrary(showtext)showtext_auto()font_add_google("Anton", "Anton") ## Format the text with HTML markuplibrary(ggtext)mytitle <-c('**<span style="font-family:Anton;">AUTOMABILES<br> HORSEPOWER<br></span> with <span style="color:orange;font-family:Anton;">4</span>, <span style="color:steelblue;font-family:Anton;">**6**</span>, <span style="color:red2;font-family:Anton;">8</span><br> cylinders**') ## Create a donut hole containing the plot title. p4 <- p3 +annotate(geom ="richtext",label = mytitle,x =0, y =-300, hjust = .48, size =4, # family = "Anton",label.color =NA, fill =NA) +# udpate the color scale to be consistent with title colorsscale_color_manual(values =c("orange", "steelblue", "red2")) p4 # A final touch on the themep5 <- p4 +theme_void() +theme(legend.position ="none",plot.margin =margin(t =30, b =10, unit ="pt")) +geom_hline(yintercept =0)p5
Continue Exploring — 🚀 one level up!
The above donut plots are produced by transforming the x aesthetic from a linear coordinate into angles in a polar coordinate. More often, however, donut plots are created by mapping the y aesthetic into angles to present “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.
Check the following awesome donut plots faceted in a U.S. map layout that visualizes the state-wise voting results of the 2016 U.S. Presidential Election between Hillary Clinton and Donald Trump.