Title: | Construct Advanced Donut Charts |
---|---|
Description: | Build donut/pie charts with 'ggplot2' layer by layer, exploiting the advantages of polar symmetry. Leverage layouts to distribute labels effectively. Connect labels to donut segments using pins. Streamline annotation and highlighting. |
Authors: | Dmitry Kibalnikov [aut, cre, cph] |
Maintainer: | Dmitry Kibalnikov <[email protected]> |
License: | MIT + file LICENSE |
Version: | 0.1.1 |
Built: | 2025-03-01 04:37:02 UTC |
Source: | https://github.com/dkibalnikov/donutsk |
Create pie or donut charts while retaining ggplot flexibility, such as leveraging faceting and palettes, and fine-tuning appearance
The function geom_donut_int()
creates visually internal donut layer as aggregation of passed values
The function geom_donut_ext()
creates visually external donut layer of passed values
geom_donut_int0()
and geom_donut_ext()
are generic geoms not supporting highlight feature
geom_donut_int0( mapping = NULL, data = NULL, stat = "donut_int", position = "identity", na.rm = FALSE, show.legend = NA, inherit.aes = TRUE, r_int = 0, r_ext = 1, hl_shift = 0.1, ... ) geom_donut_int(..., hl_col = "firebrick") geom_donut_ext0( mapping = NULL, data = NULL, stat = "donut_ext", position = "identity", na.rm = FALSE, show.legend = NA, inherit.aes = TRUE, r_int = 1.5, r_ext = 2, hl_shift = 0.1, ... ) geom_donut_ext(..., hl_col = "firebrick")
geom_donut_int0( mapping = NULL, data = NULL, stat = "donut_int", position = "identity", na.rm = FALSE, show.legend = NA, inherit.aes = TRUE, r_int = 0, r_ext = 1, hl_shift = 0.1, ... ) geom_donut_int(..., hl_col = "firebrick") geom_donut_ext0( mapping = NULL, data = NULL, stat = "donut_ext", position = "identity", na.rm = FALSE, show.legend = NA, inherit.aes = TRUE, r_int = 1.5, r_ext = 2, hl_shift = 0.1, ... ) geom_donut_ext(..., hl_col = "firebrick")
mapping |
Set of aesthetic mappings created by |
data |
The data to be displayed in this layer. There are three options: If A A |
stat |
The statistical transformation to use on the data for this
layer, either as a |
position |
Position adjustment, either as a string naming the adjustment
(e.g. |
na.rm |
If |
show.legend |
logical. Should this layer be included in the legends?
|
inherit.aes |
If |
r_int |
Internal donut radius |
r_ext |
External pie or donut radius |
hl_shift |
Sets the spacing to show highlighted segments |
... |
Other arguments passed on to |
hl_col |
Sets the color for highlighted segments. It's possible to use both simultaneously |
An object of class StatDonutInt
(inherits from Stat
, ggproto
, gg
) of length 4.
An object of class StatDonutIntHl
(inherits from Stat
, ggproto
, gg
) of length 4.
An object of class StatDonutExt
(inherits from Stat
, ggproto
, gg
) of length 4.
An object of class StatDonutExtHl
(inherits from Stat
, ggproto
, gg
) of length 4.
There are two additional aesthetics possible to use:
highlight
- optional aesthetic which expects logical (TRUE/FALSE) variable in order to highlight particular donut segments
opacity
- operates pretty much the same as alpha
but ensure more contrast colors and removes legend. Once alpha
is set opacity
does not affect a chart
None
# Create an example set.seed(1605) n <- 20 df <- dplyr::tibble( lvl1 = sample(LETTERS[1:5], n, TRUE), lvl2 = sample(LETTERS[6:24], n, TRUE), value = sample(1:20, n, TRUE), highlight_ext = sample(c(FALSE,TRUE), n, TRUE, c(.7, .3))) |> dplyr::mutate(highlight_int = ifelse(lvl1 == "A", TRUE, FALSE)) # Create a simple pie chart ggplot(df, aes(value = value, fill=lvl1)) + geom_donut_int(alpha=.6) + coord_polar(theta = "y") # Create a simple donut chart that can handle more granular data # and highlight specific segments ggplot(df, aes(value = value, fill=lvl2, highlight=highlight_ext)) + geom_donut_int(r_int=.5, alpha=.6, linewidth=.2) + coord_polar(theta = "y") + xlim(0, 1.5) # Perform data preparation tasks with `packing()` # and apply specific color packing(df, value) |> ggplot(aes(value = value, fill=lvl2, highlight=highlight_ext)) + geom_donut_int(r_int=.5, alpha=.6, linewidth=.2, col = "gray20") + coord_polar(theta = "y") + xlim(0, 1.5) # Built combined donut chart with interanl and external layers dplyr::bind_rows( # arrange by value `arrange()` = dplyr::arrange(df, lvl1, lvl2, value), # pack values for better space management `packing()` = packing(df, value, lvl1), .id = "prep_type") |> ggplot(aes(value = value, fill=lvl1)) + geom_donut_int(aes(highlight=highlight_int), alpha=.6) + geom_donut_ext(aes(opacity=lvl2, highlight=highlight_int)) + # apply facets facet_grid(~prep_type) + # style chart with palette and theme scale_fill_viridis_d(option = "inferno", begin = .1, end = .7) + theme_void() + coord_polar(theta = "y") + xlim(0, 2.5)
# Create an example set.seed(1605) n <- 20 df <- dplyr::tibble( lvl1 = sample(LETTERS[1:5], n, TRUE), lvl2 = sample(LETTERS[6:24], n, TRUE), value = sample(1:20, n, TRUE), highlight_ext = sample(c(FALSE,TRUE), n, TRUE, c(.7, .3))) |> dplyr::mutate(highlight_int = ifelse(lvl1 == "A", TRUE, FALSE)) # Create a simple pie chart ggplot(df, aes(value = value, fill=lvl1)) + geom_donut_int(alpha=.6) + coord_polar(theta = "y") # Create a simple donut chart that can handle more granular data # and highlight specific segments ggplot(df, aes(value = value, fill=lvl2, highlight=highlight_ext)) + geom_donut_int(r_int=.5, alpha=.6, linewidth=.2) + coord_polar(theta = "y") + xlim(0, 1.5) # Perform data preparation tasks with `packing()` # and apply specific color packing(df, value) |> ggplot(aes(value = value, fill=lvl2, highlight=highlight_ext)) + geom_donut_int(r_int=.5, alpha=.6, linewidth=.2, col = "gray20") + coord_polar(theta = "y") + xlim(0, 1.5) # Built combined donut chart with interanl and external layers dplyr::bind_rows( # arrange by value `arrange()` = dplyr::arrange(df, lvl1, lvl2, value), # pack values for better space management `packing()` = packing(df, value, lvl1), .id = "prep_type") |> ggplot(aes(value = value, fill=lvl1)) + geom_donut_int(aes(highlight=highlight_int), alpha=.6) + geom_donut_ext(aes(opacity=lvl2, highlight=highlight_int)) + # apply facets facet_grid(~prep_type) + # style chart with palette and theme scale_fill_viridis_d(option = "inferno", begin = .1, end = .7) + theme_void() + coord_polar(theta = "y") + xlim(0, 2.5)
The set of annotation functions utilizes layout functions to effectively distribute labels within the available space
Annotations are streamlined by leveraging pre-calculated special variables such as .sum
, .mean
, and .n
(see Details).
The function geom_label_int()
creates geom_label
-like internal donut layer as aggregation of passed values
The function geom_text_int()
creates geom_text
-like internal donut layer as aggregation of passed values
The function geom_label_ext()
creates geom_label
-like external donut layer of passed values
The function geom_text_ext()
creates geom_text
-like external donut layer of passed values
geom_label_int( mapping = NULL, data = NULL, stat = "label_int", position = "identity", na.rm = FALSE, show.legend = NA, inherit.aes = TRUE, r = 1, ... ) geom_text_int( mapping = NULL, data = NULL, stat = "text_int", position = "identity", na.rm = FALSE, show.legend = NA, inherit.aes = TRUE, r = 1, ... ) geom_label_ext( mapping = NULL, data = NULL, stat = "label_ext", position = "identity", na.rm = FALSE, show.legend = NA, inherit.aes = TRUE, layout = circle(), ... ) geom_text_ext( mapping = NULL, data = NULL, stat = "text_ext", position = "identity", na.rm = FALSE, show.legend = NA, inherit.aes = TRUE, layout = circle(), ... )
geom_label_int( mapping = NULL, data = NULL, stat = "label_int", position = "identity", na.rm = FALSE, show.legend = NA, inherit.aes = TRUE, r = 1, ... ) geom_text_int( mapping = NULL, data = NULL, stat = "text_int", position = "identity", na.rm = FALSE, show.legend = NA, inherit.aes = TRUE, r = 1, ... ) geom_label_ext( mapping = NULL, data = NULL, stat = "label_ext", position = "identity", na.rm = FALSE, show.legend = NA, inherit.aes = TRUE, layout = circle(), ... ) geom_text_ext( mapping = NULL, data = NULL, stat = "text_ext", position = "identity", na.rm = FALSE, show.legend = NA, inherit.aes = TRUE, layout = circle(), ... )
mapping |
Set of aesthetic mappings created by |
data |
The data to be displayed in this layer. There are three options: If A A |
stat |
The statistical transformation to use on the data for this
layer, either as a |
position |
Position adjustment, either as a string, or the result of
a call to a position adjustment function. Cannot be jointly specified with
|
na.rm |
If |
show.legend |
logical. Should this layer be included in the legends?
|
inherit.aes |
If |
r |
Sets the radius to place label or text for internal donut |
... |
Other arguments passed on to |
layout |
The layout function to effectively display text and labels |
An object of class StatLabelInt
(inherits from Stat
, ggproto
, gg
) of length 3.
An object of class StatTextInt
(inherits from Stat
, ggproto
, gg
) of length 3.
An object of class StatLabelExt
(inherits from Stat
, ggproto
, gg
) of length 3.
An object of class StatTextExt
(inherits from Stat
, ggproto
, gg
) of length 3.
The label functions supports glue::glue()
for convenient label construction like Total: {.sum}
,
where .sum
is pre-calculated variable. You can still use glue::glue()
or paste()
functions to pass data.frame fields for label construction.
In addition to generic aesthetics like color
, fill
, alpha
, etc., the following list of pre-calculated variables
is available for geom_label_int()
and geom_text_int()
:
.sum
: Summation of the value field
.mean
: Mean of the value field
.median
: Median of the value field
.n
: Observation count of the value field
.prc
: Percentage of the value field
For geom_label_ext()
and geom_text_ext()
, which are suitable for external donut labels, the following list of
pre-calculated variables is available:
.prc
: Percentage of the value field for the entire multiplicity
.prc_grp
: Percentage of the value field for the group defined by fill
None
# Create an example data set n <- 30 set.seed(2021) df <- dplyr::tibble( lvl1 = sample(LETTERS[1:5], n, TRUE), lvl2 = sample(LETTERS[6:24], n, TRUE), value = sample(1:20, n, TRUE), highlight_ext = sample(c(FALSE,TRUE), n, TRUE, c(.9, .1))) |> dplyr::mutate(highlight_int = dplyr::if_else(lvl1 == "A", TRUE, FALSE)) # Starting plot with doubled donuts and annotations for internal one p <- dplyr::group_by(df, lvl1, lvl2, highlight_ext, highlight_int) |> dplyr::summarise(value = sum(value), .groups = "drop") |> packing(value, lvl1) |> ggplot(aes(value = value, fill = lvl1)) + geom_donut_int(aes(highlight = highlight_int), alpha=.5, r_int=.25) + geom_text_int(lineheight = .8, r=1.2, show.legend = FALSE, aes(label = "Sum {fill}:\n{.sum}-{scales::percent(.prc)}", col=lvl1)) + geom_donut_ext(aes(opacity = lvl2, highlight = highlight_ext)) + scale_fill_viridis_d(option = "inferno", begin = .1, end = .7) + scale_color_viridis_d(option = "inferno", begin = .1, end = .7) + guides(alpha=guide_legend(ncol = 2), fill=guide_legend(ncol = 2)) + theme_void() + theme(legend.position = "inside", legend.position.inside = c(0.1, 0.9)) p + coord_radial(theta = "y", expand = FALSE, rotate_angle = FALSE) # Add labels to external donut as percent inside group p + coord_radial(theta = "y", expand = FALSE, rotate_angle = FALSE) + geom_label_ext(aes(label=paste0(lvl2, ": {scales::percent(.prc_grp)}")), show.legend = FALSE, size=3, col="white") # Leverage ggplot2 feature for labels p + coord_radial(theta = "y", expand = FALSE, rotate_angle = TRUE) + geom_label_ext(aes(label=paste0(lvl2, ": {scales::percent(.prc)}")), show.legend = FALSE, size=3, col="white", angle=90, layout = circle()) # Leverage another layout p + coord_radial(theta = "y", expand = FALSE, rotate_angle = FALSE) + geom_label_ext(aes(label=paste0(lvl2, ": {scales::percent(.prc_grp)}")), show.legend = FALSE, size=3, col="white", layout = tv(thinner = TRUE, thinner_gap = 0.15))
# Create an example data set n <- 30 set.seed(2021) df <- dplyr::tibble( lvl1 = sample(LETTERS[1:5], n, TRUE), lvl2 = sample(LETTERS[6:24], n, TRUE), value = sample(1:20, n, TRUE), highlight_ext = sample(c(FALSE,TRUE), n, TRUE, c(.9, .1))) |> dplyr::mutate(highlight_int = dplyr::if_else(lvl1 == "A", TRUE, FALSE)) # Starting plot with doubled donuts and annotations for internal one p <- dplyr::group_by(df, lvl1, lvl2, highlight_ext, highlight_int) |> dplyr::summarise(value = sum(value), .groups = "drop") |> packing(value, lvl1) |> ggplot(aes(value = value, fill = lvl1)) + geom_donut_int(aes(highlight = highlight_int), alpha=.5, r_int=.25) + geom_text_int(lineheight = .8, r=1.2, show.legend = FALSE, aes(label = "Sum {fill}:\n{.sum}-{scales::percent(.prc)}", col=lvl1)) + geom_donut_ext(aes(opacity = lvl2, highlight = highlight_ext)) + scale_fill_viridis_d(option = "inferno", begin = .1, end = .7) + scale_color_viridis_d(option = "inferno", begin = .1, end = .7) + guides(alpha=guide_legend(ncol = 2), fill=guide_legend(ncol = 2)) + theme_void() + theme(legend.position = "inside", legend.position.inside = c(0.1, 0.9)) p + coord_radial(theta = "y", expand = FALSE, rotate_angle = FALSE) # Add labels to external donut as percent inside group p + coord_radial(theta = "y", expand = FALSE, rotate_angle = FALSE) + geom_label_ext(aes(label=paste0(lvl2, ": {scales::percent(.prc_grp)}")), show.legend = FALSE, size=3, col="white") # Leverage ggplot2 feature for labels p + coord_radial(theta = "y", expand = FALSE, rotate_angle = TRUE) + geom_label_ext(aes(label=paste0(lvl2, ": {scales::percent(.prc)}")), show.legend = FALSE, size=3, col="white", angle=90, layout = circle()) # Leverage another layout p + coord_radial(theta = "y", expand = FALSE, rotate_angle = FALSE) + geom_label_ext(aes(label=paste0(lvl2, ": {scales::percent(.prc_grp)}")), show.legend = FALSE, size=3, col="white", layout = tv(thinner = TRUE, thinner_gap = 0.15))
A pre-processed subset of GDP data from the World Bank
GDP, PPP means gross domestic product based on purchasing power parity
current international $ means actual (not adjusted to inflation) US dollars
GDP
GDP
GDP
A data frame with 6,004 rows and 5 columns:
Year
Country name
Region hierarchy
3 letter ISO region codes
GDP, PPP (current international $)
...
https://data.worldbank.org/indicator/NY.GDP.MKTP.PP.CD
The layout functions help to streamline displaying text and labels geoms without overlapping effectively leveraging space available for pie and donut charts
tv()
- The function builds layout resembled an old-fashioned TV screen
petal()
- The function builds layout resembled flower with petals
circle()
- The function builds circle layout
eye()
- The function builds two-sided layout
tv( scale_x = 1.5, scale_y = 1.5, bend_x = 1, bend_y = 1, thinner = FALSE, thinner_gap = 0.1 ) petal( rotate = 0, n = 4, scale = 2.5, bend = 0.3, thinner = FALSE, thinner_gap = 0.1 ) circle(r = 2.5, thinner = FALSE, thinner_gap = 0.1) eye(scale_x = 2, bend_x = 1, alpha = 90, clove = 0.5)
tv( scale_x = 1.5, scale_y = 1.5, bend_x = 1, bend_y = 1, thinner = FALSE, thinner_gap = 0.1 ) petal( rotate = 0, n = 4, scale = 2.5, bend = 0.3, thinner = FALSE, thinner_gap = 0.1 ) circle(r = 2.5, thinner = FALSE, thinner_gap = 0.1) eye(scale_x = 2, bend_x = 1, alpha = 90, clove = 0.5)
scale_x |
Scales the layout in horizontal perspective |
scale_y |
Scales the layout in vertical perspective |
bend_x |
Adjusts the bend level in horizontal perspective |
bend_y |
Adjusts the bend level in vertical perspective |
thinner |
Distributes text or label elements across two different levels |
thinner_gap |
Sets the spacing between thinner levels |
rotate |
Rotates the layout clockwise |
n |
Sets the number of petals in the layout |
scale |
Scales the layout |
bend |
Manages the bending level |
r |
Sets the radius of the layout circle |
alpha |
Defines the angle of distribution in horizontal perspective. Pick up value from degree interval (0, 180) |
clove |
Determines the distribution proportion between the left and right-hand parts. Default value is 0.5. There should be numeric value from interval (0, 1) e.g. 0.4 denotes 40% cases on the right hand and 60% cases on the left hand |
Layout functions return layout function i.e. a function that takes a vector of angles and returns a numeric radius vector giving a position for each input value.
Layout functions are designed to be used with the layout argument of donutsk functions.
Utilized in the following functions: geom_label_ext, geom_text_ext, geom_pin
# Render multiple layouts simultaneously list(petal_2n = petal(n = 2), petal_3n = petal(n = 3, rotate = 180), petal_4n = petal(n = 4), tv_base = tv(), tv_ext = tv(bend_x = 0, bend_y = 0, thinner = TRUE)) |> lapply(function(x){ rlang::exec(x, 1:300/300) |> dplyr::tibble(r = _) |> dplyr::mutate(theta = 1:300/300) }) |> dplyr::bind_rows(.id = "layouts") |> ggplot(aes(x=r, y=theta, col = layouts)) + geom_point(alpha = .3) + coord_polar(theta = "y") + xlim(0, 3.5) # The eye() layout generates table as an output n <- 20 theta <- 1:n/n dplyr::tibble( theta = theta, lbl = paste0("sample: ", sample(LETTERS, n, TRUE)) ) |> dplyr::bind_cols(lt = eye()(theta)) |> ggplot(aes(x=x, y=y)) + geom_point(aes(x=1, y=theta)) + geom_point() + geom_segment(aes(x=1, xend=x, y=theta, yend=y), linewidth=.2) + geom_label(aes(label=lbl, hjust=dplyr::if_else(theta > 0.5, 1, 0)), nudge_x =.2) + coord_polar(theta = "y") + xlim(0, 5) + ylim(0, 1)
# Render multiple layouts simultaneously list(petal_2n = petal(n = 2), petal_3n = petal(n = 3, rotate = 180), petal_4n = petal(n = 4), tv_base = tv(), tv_ext = tv(bend_x = 0, bend_y = 0, thinner = TRUE)) |> lapply(function(x){ rlang::exec(x, 1:300/300) |> dplyr::tibble(r = _) |> dplyr::mutate(theta = 1:300/300) }) |> dplyr::bind_rows(.id = "layouts") |> ggplot(aes(x=r, y=theta, col = layouts)) + geom_point(alpha = .3) + coord_polar(theta = "y") + xlim(0, 3.5) # The eye() layout generates table as an output n <- 20 theta <- 1:n/n dplyr::tibble( theta = theta, lbl = paste0("sample: ", sample(LETTERS, n, TRUE)) ) |> dplyr::bind_cols(lt = eye()(theta)) |> ggplot(aes(x=x, y=y)) + geom_point(aes(x=1, y=theta)) + geom_point() + geom_segment(aes(x=1, xend=x, y=theta, yend=y), linewidth=.2) + geom_label(aes(label=lbl, hjust=dplyr::if_else(theta > 0.5, 1, 0)), nudge_x =.2) + coord_polar(theta = "y") + xlim(0, 5) + ylim(0, 1)
Arrange data to distribute small values further apart from each other
packing(.data, value, level = NULL)
packing(.data, value, level = NULL)
.data |
A data frame, data frame extension (e.g. a tibble), or a lazy data frame (e.g. from dbplyr or dtplyr). |
value |
A .data field which contains values to distribute |
level |
A .data grouping field for distribution |
An object of the same type as .data.
# Create an example n <- 20 df <- dplyr::tibble( lvl1 = sample(LETTERS[1:5], n, TRUE), lvl2 = sample(LETTERS[6:24], n, TRUE), value = sample(1:20, n, TRUE) ) # Arrange all values packing(df, value) # Arrange values within values packing(df, value, lvl1)
# Create an example n <- 20 df <- dplyr::tibble( lvl1 = sample(LETTERS[1:5], n, TRUE), lvl2 = sample(LETTERS[6:24], n, TRUE), value = sample(1:20, n, TRUE) ) # Arrange all values packing(df, value) # Arrange values within values packing(df, value, lvl1)
The set of functions served to connect text or labels with donut segments
geom_pin_line()
- builds curved line to linl label with donut segment
geom_pin_head()
- builds stylish point heads for pins
geom_pin()
- handy wrapper for geom_pin_line()
and geom_pin_head()
geom_pin_line( mapping = NULL, data = NULL, stat = "pin", position = "identity", na.rm = FALSE, show.legend = NA, inherit.aes = TRUE, r = 1.5, cut = 0.1, layout = circle(), ... ) geom_pin_head( mapping = NULL, data = NULL, stat = "point", position = "identity", na.rm = FALSE, show.legend = NA, inherit.aes = TRUE, r = 1.5, cut = 0.1, layout = circle(), ... ) geom_pin(..., head = TRUE)
geom_pin_line( mapping = NULL, data = NULL, stat = "pin", position = "identity", na.rm = FALSE, show.legend = NA, inherit.aes = TRUE, r = 1.5, cut = 0.1, layout = circle(), ... ) geom_pin_head( mapping = NULL, data = NULL, stat = "point", position = "identity", na.rm = FALSE, show.legend = NA, inherit.aes = TRUE, r = 1.5, cut = 0.1, layout = circle(), ... ) geom_pin(..., head = TRUE)
mapping |
Set of aesthetic mappings created by |
data |
The data to be displayed in this layer. There are three options: If A A |
stat |
The statistical transformation to use on the data for this
layer, either as a |
position |
Position adjustment, either as a string naming the adjustment
(e.g. |
na.rm |
If |
show.legend |
logical. Should this layer be included in the legends?
|
inherit.aes |
If |
r |
The radius where donut is placed |
cut |
Sets additional two-sided gap for pins |
layout |
The layout function to effectively display text and labels.
Obviously it's better to have the same as for |
... |
Parameters to be passed to |
head |
Boolean - defines whether to add pin head |
An object of class StatPinLine
(inherits from Stat
, ggproto
, gg
) of length 3.
An object of class StatPinHead
(inherits from Stat
, ggproto
, gg
) of length 3.
None
n <- 30 set.seed(2021) df <- dplyr::tibble( lvl1 = sample(LETTERS[1:5], n, TRUE), lvl2 = sample(LETTERS[6:24], n, TRUE), value = sample(1:20, n, TRUE), highlight_ext = sample(c(FALSE,TRUE), n, TRUE, c(.9, .1))) |> dplyr::mutate(highlight_int = dplyr::if_else(lvl1 == "A", TRUE, FALSE)) # Starting plot with doubled donuts and annotations for internal one p <- dplyr::group_by(df, lvl1, lvl2, highlight_ext, highlight_int) |> dplyr::summarise(value = sum(value), .groups = "drop") |> packing(value, lvl1) |> ggplot(aes(value = value, fill = lvl1)) + geom_donut_int(aes(highlight = highlight_int), alpha=.5, r_int = .25) + geom_label_int(aes(label = "Sum {fill}:\n{.sum}-{scales::percent(.prc)}"), alpha = .6, col = "white", size = 3, r=1.2) + geom_donut_ext(aes(opacity = lvl2, highlight = highlight_ext)) + scale_fill_viridis_d(option = "inferno", begin = .1, end = .7) + guides(alpha = guide_legend(ncol = 2), fill = guide_legend(ncol = 2)) + theme_void() + theme(legend.position = "none") p + coord_radial(theta = "y", expand = FALSE, rotate_angle = FALSE) # Add labels to external donut as percent inside group p + coord_radial(theta = "y", expand = FALSE, rotate_angle = FALSE) + geom_label_ext(aes(label = paste0(lvl2, ": {scales::percent(.prc_grp)}")), show.legend = FALSE, size=3, col="white") + geom_pin(size = .5, linewidth=.1, show.legend = FALSE, cut = .2) # Leverage tv() layout p + coord_radial(theta = "y", expand = FALSE, rotate_angle = FALSE) + geom_label_ext(aes(label = paste0(lvl2, ":{scales::percent(.prc_grp)}")), show.legend = FALSE, size=3, col="white", layout = tv(thinner = TRUE, thinner_gap = .15)) + geom_pin(size = .5, linewidth=.1, show.legend = FALSE, cut = .2, layout = tv(thinner = TRUE, thinner_gap = .15)) # Leverage another layout p + coord_radial(theta = "y", expand = FALSE, rotate_angle = FALSE) + geom_label_ext(aes(label = paste0(lvl2, ": {scales::percent(.prc_grp)}")), show.legend = FALSE, size=3, col="white", layout = eye()) + geom_pin(size = .5, linewidth=.1, show.legend = FALSE, layout = eye())
n <- 30 set.seed(2021) df <- dplyr::tibble( lvl1 = sample(LETTERS[1:5], n, TRUE), lvl2 = sample(LETTERS[6:24], n, TRUE), value = sample(1:20, n, TRUE), highlight_ext = sample(c(FALSE,TRUE), n, TRUE, c(.9, .1))) |> dplyr::mutate(highlight_int = dplyr::if_else(lvl1 == "A", TRUE, FALSE)) # Starting plot with doubled donuts and annotations for internal one p <- dplyr::group_by(df, lvl1, lvl2, highlight_ext, highlight_int) |> dplyr::summarise(value = sum(value), .groups = "drop") |> packing(value, lvl1) |> ggplot(aes(value = value, fill = lvl1)) + geom_donut_int(aes(highlight = highlight_int), alpha=.5, r_int = .25) + geom_label_int(aes(label = "Sum {fill}:\n{.sum}-{scales::percent(.prc)}"), alpha = .6, col = "white", size = 3, r=1.2) + geom_donut_ext(aes(opacity = lvl2, highlight = highlight_ext)) + scale_fill_viridis_d(option = "inferno", begin = .1, end = .7) + guides(alpha = guide_legend(ncol = 2), fill = guide_legend(ncol = 2)) + theme_void() + theme(legend.position = "none") p + coord_radial(theta = "y", expand = FALSE, rotate_angle = FALSE) # Add labels to external donut as percent inside group p + coord_radial(theta = "y", expand = FALSE, rotate_angle = FALSE) + geom_label_ext(aes(label = paste0(lvl2, ": {scales::percent(.prc_grp)}")), show.legend = FALSE, size=3, col="white") + geom_pin(size = .5, linewidth=.1, show.legend = FALSE, cut = .2) # Leverage tv() layout p + coord_radial(theta = "y", expand = FALSE, rotate_angle = FALSE) + geom_label_ext(aes(label = paste0(lvl2, ":{scales::percent(.prc_grp)}")), show.legend = FALSE, size=3, col="white", layout = tv(thinner = TRUE, thinner_gap = .15)) + geom_pin(size = .5, linewidth=.1, show.legend = FALSE, cut = .2, layout = tv(thinner = TRUE, thinner_gap = .15)) # Leverage another layout p + coord_radial(theta = "y", expand = FALSE, rotate_angle = FALSE) + geom_label_ext(aes(label = paste0(lvl2, ": {scales::percent(.prc_grp)}")), show.legend = FALSE, size=3, col="white", layout = eye()) + geom_pin(size = .5, linewidth=.1, show.legend = FALSE, layout = eye())