MYaseen208
MYaseen208

Reputation: 23938

R Pie Donut chart with facet functionality

The following code works fine and produces the required graph as given below:

library(tidyverse)
library(ggiraphExtra)

library(moonBook)

ggPieDonut(data = acs, mapping = aes(pies = Dx, donuts = smoking), interactive = TRUE)

enter image description here

Wondering how to construct Pie Donut chart with facet functionality. My attempt is below:

ggPieDonut(data = acs, mapping = aes(pies = Dx, donuts = smoking), interactive = TRUE) +
  facet_wrap(facets = vars(sex))
NULL

Upvotes: 2

Views: 2187

Answers (2)

jpiversen
jpiversen

Reputation: 3222

The problem

The code in your attempt doesn't work because when interactive = TRUE, ggPieDonut() doesn't return a ggplot, but a htmlwidget:

ggPieDonut(
  data = acs, 
  mapping = aes(pies = Dx, donuts = smoking), 
  interactive = TRUE
) %>% class()
#> [1] "girafe"     "htmlwidget"

And facet_wrap() only works with ggplots.

If you change to interactive = FALSE you get another problem:

ggPieDonut(
  data = acs, 
  mapping = aes(pies = Dx, donuts = smoking), 
  interactive = FALSE
) + 
  facet_wrap(~sex)
#> Error in `combine_vars()`:
#> ! At least one layer must contain all faceting variables: `sex`.

The geoms doesn't contain both values of sex, so facet_wrap() doesn't know how to facet on it.

Possible workaround

A solution is to create two plots on different subsets of the data, and use patchwork to combine the two plots:

library(patchwork)

p1 <- 
  acs %>% 
  filter(sex == "Male") %>% 
  ggPieDonut(mapping = aes(pies = Dx, donuts = smoking), interactive = FALSE) + 
  labs(title = "Male")

p2 <- 
  acs %>% 
  filter(sex == "Female") %>% 
  ggPieDonut(mapping = aes(pies = Dx, donuts = smoking), interactive = FALSE) + 
  labs(title = "Female")

p1 + p2

Output:

The two plots combined


Update 1 - as a function

As @MikkoMarttila suggested, it might be better to create this as a function. If I were to reuse the function, I would probably write it like this:

make_faceted_plot <- function(data, pie, donut, facet_by) {
  data %>% 
    dplyr::pull( {{facet_by}} ) %>% 
    unique() %>% 
    purrr::map(
      ~ data %>% 
        dplyr::filter( {{facet_by}} == .x) %>% 
        ggiraphExtra::ggPieDonut(
          ggplot2::aes(pies = {{pie}}, donuts = {{donut}}), 
          interactive = FALSE
        ) + 
        ggplot2::labs(title = .x)
    ) %>% 
    patchwork::wrap_plots() 
}

This can then be used to facet on however many categories we want, and on any dataset, for example:

library(patchwork)
library(dplyr)

# Expandable example data
df <- data.frame(
  eyes = sample(c("Blue", "Bown", "Green"), size = 100, replace = TRUE),
  hair = sample(c("blonde", "brunette", "raven"), size = 100, replace = TRUE),
  sex = sample(c("male", "female"), size = 100, replace = TRUE)
)

df %>% 
  make_faceted_plot(
    pie = eyes,
    donut = sex,
    facet_by = hair
  )

Another combined plot

Again, as suggested by @MikkoMarttila, this can be piped into ggiraph::girafe(code = print(.)) to add some interactivity.


Update 2 - change labels

The OP wants the labels to be the same in the static and interactive plots.

The labels for both the static and interactive plots are stored inside <the plot object>$plot_env. From here it's just a matter of looking around, and replacing the static labels with the interactive ones. Since the interactive labels contains HTML-tags, we do some cleaning first. I would wrap this in a function, as such:

change_label <- function(plot) {
  
  plot$plot_env$Pielabel <- 
    plot$plot_env$data2$label %>% 
    stringr::str_replace_all("<br>", "\n") %>% 
    stringr::str_replace("\\(", " \\(")
  
  plot$plot_env$label2 <- 
    plot$plot_env$dat1$label %>% 
    stringr::str_replace_all("<br>", "\n") %>% 
    stringr::str_replace("\\(", " \\(") %>% 
    stringr::str_remove("(NSTEMI\\n|STEMI\\n|Unstable Angina\n)")
  
  
  plot
}

By adding this function to make_plot() we get the labels we want:

make_faceted_plot <- function(data, pie, donut, facet_by) {
  data %>% 
    dplyr::pull( {{facet_by}} ) %>% 
    unique() %>% 
    purrr::map(
      ~ data %>% 
        dplyr::filter( {{facet_by}} == .x) %>% 
        ggiraphExtra::ggPieDonut(
          ggplot2::aes(pies = {{pie}}, donuts = {{donut}}), 
          interactive = FALSE
        ) + 
        ggplot2::labs(title = .x)
    ) %>% 
    purrr::map(change_label) %>% # <-- added change_label() here
    patchwork::wrap_plots() 
}

acs %>% 
  make_faceted_plot(
    pie = Dx,
    donut = smoking,
    facet_by = sex
  ) 

Plots with correct label

Upvotes: 2

Mikko Marttila
Mikko Marttila

Reputation: 11908

It seems @jpiversen and I had the same idea about this, so I initially didn’t post my answer after seeing theirs. However, by request of the OP, and in case it proves useful in some way, here’s my take on this:


I think the short answer is: you can’t.

There’s no option for facetting in the function call, and the resulting ggplot2 object has had it’s data modified enough to not have access to the original columns anymore, making ggplot facetting afterwards impossible.

However, you can get in the vicinity of facetting by constructing the plots separately for each level of the variable you wanted to facet by, and then assembling the plots together.

Here’s how I’d approach that:

library(tidyverse)
library(ggiraphExtra)

library(moonBook)

make_plot <- function(data) {
  ggPieDonut(data = data, mapping = aes(pies = Dx, donuts = smoking))
}

plots <- by(acs, acs$sex, make_plot)
combined_plots <- patchwork::wrap_plots(plots) +
  patchwork::plot_annotation(tag_levels = list(names(plots)))

combined_plots

I don’t know how to do the combination step if you specify interactive = TRUE, but as a workaround some approximation of interactivity could be also reached with a simple girafe() call on the combined plots:

ggiraph::girafe(code = print(combined_plots))

Upvotes: 2

Related Questions