Search code examples
rggplot2

How to reorder a stacked bar chart in ggplot2?


I know this question has been asked and answered so many time over but I can't find the answer that applies to my custom function. I've modified gtExtras::gt_plt_bar_stack() and have everything in place except the bars aren't ordered correctly. The correct bar is colored in my example below, but it isn't in the middle as it should be based on the data frame supplied to it.

incorrect bar chart

Code:

library(ggplot2)
library(gt)
library(dplyr)
library(glue)

# CUSTOM FUNCTION ---
gt_bar_stack_minimal <- function(gt_object, column) {
  stopifnot("Table must be a gt_tbl" = "gt_tbl" %in% class(gt_object))
  
  var_sym <- rlang::enquo(column)  
  col_vals <- gt_index(gt_object, {{ column }})  
  
  bar_fx <- function(values) {
    if (is.null(values) || length(values) < 3) return("<div></div>")
    
    numeric_vals <- as.numeric(values[1:3])
    middle_color <- values[4] %||% "black"  # Default to black if no color is provided
    
    palette <- c("lightgrey", middle_color, "lightgrey")  # First and last are always lightgrey
    
    df <- tibble(x = numeric_vals, fill = palette, order = 1:3) # How do I presever this order
    
    # Troubleshooting order
    print(df)
    
    plot_out <- ggplot(df, aes(x = factor(1), y = x, fill = I(fill))) +
      # geom_col(width = 1, color = "white") +
      geom_bar(stat = "identity", width = 1, color = "white") +
      coord_flip() +
      theme_void() +
      theme(legend.position = "none", plot.margin = margin(0, 0, 0, 0, "pt"))
    
    out_name <- tempfile(fileext = ".svg")
    ggsave(out_name, plot = plot_out, dpi = 25.4, height = 5, width = 70, units = "mm", device = "svg")
    
    img_plot <- readLines(out_name) %>% paste0(collapse = "") %>% gt::html()
    on.exit(file.remove(out_name), add = TRUE)
    
    img_plot
  }
  
  text_transform(gt_object, locations = cells_body({{ column }}), fn = function(x) lapply(col_vals, bar_fx))
}

# EXAMPLE USAGE ---
ex_df <- tibble(
  x = c("Example 1", "Example 2", "Example 3", "Example 4"),
  list_data = list(c(30, 20, 50, "blue"), c(30, 30, 40, "red"), c(30, 40, 30, "purple"), c(30, 50, 20, "orange"))
)

ex_tab <- ex_df %>%
  gt() %>%
  gt_bar_stack_minimal(column = list_data)

ex_tab

Solution

  • You have to map order on the group aes to stack the bars in that order. Otherwise the group aes is set and the bars get stacked according to the fill aes, i.e. the color names. Also note that I dropped the coord_flip by switching x and y`:

    plot_out <- ggplot(df, aes(x = x, y = factor(1), fill = I(fill), group = order)) +
      geom_col(
        width = 1,
        color = "white",
        position = position_stack(reverse = TRUE)
      ) +
      theme_void() +
      theme(legend.position = "none", plot.margin = margin(0, 0, 0, 0, "pt"))
    

    enter image description here