rggplot2pie-chartdonut-chartggiraphextra

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

Solution

  • 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