rggplot2legend

R ggplot2 change legend display to left-to-right


I have a legend for a ggplot chart that spans two rows with several short values and one long value: legend over two rows

Currently legend 'fills' by column then by row, causing the wrapping to mess with the proportion. Is there a way to have it 'fill' by row first? So it reads like so:

Strongly disagree | Disagree | Slightly disagree |

Slightly agree | Agree | Strongly agree | 

No opportunities to observe/Prefer not to say/I dont know

or even

Strongly disagree | Disagree | Slightly disagree | Slightly agree | Agree | Strongly agree |

No opportunities to observe/Prefer not to say/I dont know

Chart code below:

temp_chart <- ggplot(data=dat_chart, aes(x=category, y=percentage, group=question, fill = factor(answer, levels = bar_order1))) +
      geom_bar(stat = "identity", position = "stack") +
      scale_x_discrete(limits=rev, labels = wrap_format(25)) +
      scale_y_continuous(labels = function(x) paste0(x, "%")) +
      scale_fill_manual(labels = ~ stringr::str_wrap(.x, width = 20), values = culture_col1) +
      geom_vline(xintercept = (0:10)+0.5) +
      coord_flip() +
      facet_wrap(~question, ncol = 1) +
      guides(fill = guide_legend(nrow = 3, ncol = 3)) +
      theme_minimal() +
      theme(strip.text = element_text(hjust = 0),
            axis.title.x=element_blank(),
            axis.title.y=element_blank(),
            legend.title=element_blank(),
            legend.position="bottom",
            panel.grid.minor = element_blank(),
            panel.grid.major = element_blank(),
            panel.border = element_rect(color = "black", fill = NA, size = 2),
            panel.spacing = unit(0.75, "lines"))

simplified data:

dput(dat_chart)
structure(list(question = c("1. Question1", 
"1. Question1", "1. Question1", 
"1. Question1", "1. Question1", 
"1. Question1", "1. Question1", 
"1. Question1", "1. Question1", 
"1. Question1", "1. Question1", 
"1. Question1", "1. Question1", 
"1. Question1", "1. Question1", 
"1. Question1", "1. Question1", 
"1. Question1", "1. Question1", 
"1. Question1", "1. Question1"
), percentage = c(3.35731414868106, 4.55635491606715, 5.03597122302158, 
13.189448441247, 47.242206235012, 26.378896882494, 0.239808153477218, 
0, 7.69230769230769, 11.5384615384615, 26.9230769230769, 30.7692307692308, 
23.0769230769231, 0, 2.02020202020202, 7.07070707070707, 13.1313131313131, 
20.2020202020202, 32.3232323232323, 25.2525252525253, 0), category = c("a", 
"a", "a", "a", "a", "a", "a", "a", 
"b", "b", "b", "b", "b", "b", "c", 
"c", "c", 
"c", "c", 
"c", "c"
), answer = c("Strongly disagree", "Disagree", "Slightly disagree", 
"Slightly agree", "Agree", "Strongly agree", "No opportunities to observe/Prefer not to say/I dont know", 
"Strongly disagree", "Disagree", "Slightly disagree", "Slightly agree", 
"Agree", "Strongly agree", "No opportunities to observe/Prefer not to say/I dont know", 
"Strongly disagree", "Disagree", "Slightly disagree", "Slightly agree", 
"Agree", "Strongly agree", "No opportunities to observe/Prefer not to say/I dont know"
)), row.names = c(NA, -21L), class = c("tbl_df", "tbl", "data.frame"
))

Solution

  • First of all, load the required packages and define a custom theme. The custom theme will simplify the plot code and can be reused if you need it for other plots, it can give your plots a common graphic look.

    I simplified the theme, to take advantage of inheritance of theme elements. From the documentation:

    Theme elements are documented together according to inheritance, read more about theme inheritance below.

    And below, section Arguments:

    axis.title.*.*⁠ inherits from ⁠axis.title.*⁠ which inherits from axis.title, which in turn inherits from text

    and

    panel.grid.*.*⁠ inherits from ⁠panel.grid.*⁠ which inherits from panel.grid, which in turn inherits from line

    suppressPackageStartupMessages({
      library(tidyverse)
      library(scales)
    })
    
    theme_so_q79721367 <- function(){ 
      theme_minimal() %+replace%
        theme(
          # theme elements in alphabetical order
          axis.title = element_blank(),
          legend.title = element_blank(),
          legend.position = "bottom",
          panel.grid = element_blank(),
          panel.border = element_rect(color = "black", fill = NA, size = 2),
          panel.spacing = unit(0.75, "lines"),
          strip.text = element_text(hjust = 0)
        )
    }
    

    Then, I define the answers' order and the bars colors variables. I have changed bar_order1 to a more descriptive variable name, culture_order1.

    culture_order1 <- c("Strongly disagree", "Disagree","Slightly disagree",
              "Slightly agree", "Agree", "Strongly agree",
              "No opportunities to observe/Prefer not to say/I dont know")
    
    culture_col1 <- setNames(
      c("#c23728", "#de6e56", "#f7dbd4", "#d1ecfa", "#63bff0", "#1984c5", "#737373"), 
      culture_order1)
    

    Now the plot.
    To answer the question, if you want to have the legend in row-first order, set byrow = TRUE in guide_legend.

    ggplot(data = dat_chart, aes(x = category, y = percentage, group = question, fill = factor(answer, levels = culture_order1))) +
      geom_bar(stat = "identity", position = "stack") +
      scale_x_discrete(limits = rev, labels = wrap_format(25)) +
      scale_y_continuous(labels = function(x) paste0(x, "%")) +
      scale_fill_manual(labels = ~ stringr::str_wrap(.x, width = 20), values = culture_col1) +
      geom_vline(xintercept = (0:10)+0.5) +
      coord_flip() +
      facet_wrap(~ question, ncol = 1) +
      guides(fill = guide_legend(nrow = 3, ncol = 3, byrow = TRUE)) +
      theme_so_q79721367()
    #> Warning: The `size` argument of `element_rect()` is deprecated as of ggplot2 3.4.0.
    #> ℹ Please use the `linewidth` argument instead.
    #> This warning is displayed once every 8 hours.
    #> Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
    #> generated.
    

    Created on 2025-08-01 with reprex v2.1.1