
{ggtricks} package is a collection of multiple
geom presenting data in the form of circle (for now, but
many more to come and not just circle oriented.) using
grammar of graphics philosophy and Cartesian coordinates
system.
You have a lot of functions to make sector charts where circle is split along its circumference, therefore each section is proportional to value it represents.
geom_pie Pie chartsgeom_donut Donut charts (Pie chart with a hole)geom_slice Part of Pie chartsgeom_donut_slice Part of Donut chartsYou also have a function, geom_series_circles() to plot
what I call series of circles, which draws for a category as many
circles and fraction of circles as needed to represent the value
represented by that category. A companion function
geom_series_text is defined to put labels at the boundary
of series circles because calculating these boundary positions can be
tedious depending on the starting angle of the fragments of circles.
You can install the stable version of ggtricks like so:
install.packages("ggtricks")or the developpement version:
devtools::install_github("abdoulma/ggtricks")geom_series_circleslibrary(tidyverse)
library(ggtricks)
my_theme <- function(...) {
theme_minimal() +
theme(
text = element_text(family = "Atkinson Hyperlegible"),
axis.text.y = element_text(color = "black", size = rel(1.5))
)
}
prod_df <- data.frame(
good = c("Potatoes", "Sugar", "Butter", "Coffee", "Rice", "Eggs", "Flour", "Tea", "Milk"),
index = c(606, 485, 204, 165, 215, 268, 267, 137, 194)
)
prod_df <- prod_df |>
mutate(
index = index / 100,
good = fct_rev(fct_inorder(good))
)
prod_df |>
ggplot() +
geom_series_circles(aes(index, good), color = "white") +
coord_equal() +
my_theme()
Of course, there is a fill mapping argument to drive
each category filling color.
prod_df |>
ggplot() +
geom_series_circles(aes(index, good, fill = good), color = "black", linewidth = 2.5) +
coord_equal() +
my_theme()
Of course, you can choose, to customize the categories labels by
setting axis.text in theme_*() function. But
the need may come to add labels at series of circles boundary positions.
There comes geom_series_text() function.
prod_df |>
ggplot() +
geom_series_circles(aes(index, good, fill = good), color = "black", linewidth = 2.5) +
geom_series_text(aes(index, good, label = index), size = 6, family = "Atkinson Hyperlegible") +
coord_equal(clip = "off") +
guides(
fill = "none",
) +
my_theme()
You can set init_angle to define fragment of circle
starting angle.
index_df <- tribble(
~article, ~index,
"Plate beef", 187,
"Bacon", 215,
"Lard", 266
)
index_df <- index_df |>
mutate(
index = index / 100
)
index_df |>
ggplot() +
geom_series_circles(aes(index, article),
init_angle = 45
# init_angle = 90
# init_angle = 145
# init_angle = 180
) +
coord_equal() +
theme_minimal()
Let’s illustrate a use of the combination of two series of circles with another example from Charts And Graphs (An introduction to graphics methods in control and analysis of statistics) by KARL G. KARSTEN, B.A.
usa_trades <- tribble(
~country, ~with_foreign, ~with_us,
"United States", 13359, 13359,
"United Kingdom", 15925, 3123,
"Canada", 2304, 1256,
"France", 7429, 1686,
"Italy", 4189, 1516,
"Netherlands", 2639, 316,
"Japan", 2421, 1420,
"Germany", 4966, 577
)
usa_trades <- usa_trades |>
mutate(
country = fct_rev(fct_inorder(country)),
across(.cols = contains("with"), \(x) x / 1e3, .names = "{.col}")
) |>
arrange(country) |>
mutate(
row_num = row_number()
)
n_rows <- nrow(usa_trades)
usa_trades |>
ggplot() +
geom_series_circles(aes(with_foreign, country), fill = "white", color = "black", linewidth = 2) +
geom_series_circles(aes(with_us, country)) +
geom_text(aes(y = row_num, label = scales::comma(with_us)), x = -1, family = "Atkinson Hyperlegible", hjust = 1) +
geom_text(aes(y = row_num, label = scales::comma(with_foreign)), x = -2, family = "Atkinson Hyperlegible", hjust = 1) +
geom_text(aes(y = row_num, label = country), x = -3.5, family = "Atkinson Hyperlegible", fontface = "bold", hjust = 1) +
annotate(geom = "text", x = -1, y = n_rows + 1, label = "Trade\n with\n U.S.", family = "Atkinson Hyperlegible", hjust = 1) +
annotate(geom = "text", x = -2, y = n_rows + 1, label = "Total\n Foreign\n Trade", family = "Atkinson Hyperlegible", hjust = 1) +
annotate(geom = "text", x = 8, y = n_rows + 1, label = "(Millions of Dollars)", family = "Atkinson Hyperlegible") +
scale_x_continuous(
limits = c(-5, 14)
) +
coord_equal(clip = "off") +
theme_minimal() +
theme(
axis.text = element_blank(),
axis.title = element_blank(),
panel.grid = element_blank(),
plot.background = element_rect(fill = "white", color = NA)
)
geom_pieinit_angleAs with geom_series_circles(), you can set the init
angle parameter to set the starting angle of your pie (here the
pie, but it is also available for donut,
slice and donut_slice.)
my_df <- data.frame(
cat = paste0("Prod ", 1:4),
val = c(87, 34, 21, 8)
)
categories_fills <- c(
"Prod 1" = "#3E71EC",
"Prod 2" = "#A9A9A9",
"Prod 3" = "#7942A6",
"Prod 4" = "#F7324B"
)my_df |>
ggplot() +
geom_pie(aes(cat = cat, val = val, fill = cat),
init_angle = 0
# init_angle = 60,
# init_angle = 120,
# init_angle = 180
) +
coord_equal() +
scale_fill_manual(
values = categories_fills
) +
theme_minimal()
spotlight_max &
spotlight_positionIf you want the category with the max value to determine the slices
positions, you can set the spotlight_max parameter to
true. Then the category with the max value will be placed
at spotlight_position (by default top, others
possible values are: right, bottom and
left.)
my_df |>
ggplot() +
geom_pie(aes(cat = cat, val = val),
spotlight_max = TRUE,
spotlight_position = "top",
# spotlight_position = "right"
# spotlight_position = "bottom"
# spotlight_position = "left"
) +
coord_equal() +
scale_fill_manual(
values = categories_fills
) +
theme_minimal()
spotlight_catMaybe, you want a specific category to drive the slices positions
rather than the category with the maximum value ? Then come the
spotlight_cat parameter to define the driving category.
Also here you can combine the spotlight_cat parameter value
with spotlight_position to specify its position.
my_df |>
ggplot() +
geom_pie(aes(cat = cat, val = val, fill = cat),
spotlight_cat = "Prod 1",
spotlight_position = "top"
) +
coord_equal() +
scale_fill_manual(
values = categories_fills,
guide = "none"
) +
theme_minimal()
labelsAs I know that it can be difficult to know the coordinates of the
center positions of the category slices, I define a default
label mapping which will place the provided labels at this
position. When label mapping is defined, you can define
labels_with_tick parameter to TRUE to add tick
mark at the centers positions of the slices.
my_df |>
ggplot() +
geom_pie(
aes(cat = cat, val = val, fill = cat, label = cat)
# labels_with_ticks = TRUE
) +
coord_equal() +
scale_fill_manual(
values = categories_fills,
guide = "none"
) +
theme_minimal()
geom_donutDonut is just pie with a hole in it. There are two parameters
r1 and r2 to define thickness of the
donut.
my_df |>
ggplot() +
geom_donut(aes(cat = cat, val = val, fill = cat),
r1 = 1, r2 = .65
# r1 = 1 , r2 = .35
) +
coord_equal() +
scale_fill_manual(
values = categories_fills,
guide = "none"
) +
theme_minimal()
All others parameters available for geom_pie are also
here.
geom_sliceIt is a portion of pie, by default a half (180 deg). You can set the
slice_angle portion as needed.
my_df |>
ggplot() +
geom_slice(aes(cat = cat, val = val, fill = cat),
slice_angle = 180 # ,
# slice_angle = 120
) +
coord_equal() +
scale_fill_manual(
values = categories_fills,
guide = "none"
) +
theme_minimal()
Also here, you can set the starting angle position with
init_angle. Note here that there are no
spotlight_max, spotlight_cat parameters, since
we are not drawing a complete circle (but theoretically you can, if you
set slice_angle to 360, which means a
pie.)
my_df |>
ggplot() +
geom_slice(aes(cat = cat, val = val, fill = cat),
init_angle = 30 # ,
# init_angle = 90
) +
coord_equal() +
scale_fill_manual(
values = categories_fills,
guide = "none"
) +
theme_minimal()
You can however set the slice position with
slice_position(possible values are: top,
right, bottom, and left). Soon, I
will post more detailed examples on the package website: https://abdoulma.github.io/ggtricks/.
geom_donut_sliceIt is a slice of donut plot. As a geom_donut, it is
driven by 2 radii and as a slice plot, it has a defined slice angle.
my_df |>
ggplot() +
geom_donut_slice(aes(cat = cat, val = val, fill = cat),
r1 = 1, r2 = .65
# r1 = 1, r2 = .35,
# slice_angle = 90
# slice_angle = 120
# slice_angle = 180
) +
coord_equal() +
scale_fill_manual(
values = categories_fills,
guide = "none"
) +
theme_minimal()
geom_slice_donut also has special parameter
link_with_origin, if you want to connect the donut slice
boundaries with origin.
my_df |>
ggplot() +
geom_donut_slice(aes(cat = cat, val = val, fill = cat),
r1 = 1, r2 = .65,
slice_angle = 120,
slice_position = "top",
link_with_origin = TRUE
) +
coord_equal(clip = "off") +
scale_fill_manual(
values = categories_fills,
guide = "none"
) +
theme_minimal()
As you might have noticed, to generate circle, I use
coord_equal(), using coord_cartesian() will
zoom the plot, not generating a appealing circle shape even if the
underlying drawn plot is a circle. So, we fix, the
aspect ratio to force :
the physical representation of data units on the axes.
according to the official documentation.
Of course, you shouldn’t edit the default ratio = 1 that
ensures that one unit on x-axis is the same length as one
unit on the y-axis.
When using geom_series_circles(), the desire will come
one day to combine it with facet_wrap() or
facet_grid or any faceting function, you should
not, or not the way you envision.
Since we are using coord_equal(), you won’t be able to
set scales parameter, which I strongly suspect you to try
to do. So for the moment, I advise you not to do so. However, I will
provide some tips to go through those restrictions on package website https://abdoulma.github.io/ggtricks/
In the coming weeks, additional features will be added to current
geoms:
donut and donut_slice.As announced at start, I am not limiting the package to sector
charts, so additional geom styles will be added, and if you
have suppositions, fee free to open an issue, I am open to all
contributions.