0

Issue

I am sure this is a very fix for someone who has done it before. I want to have a different spacing between legend key box 2 and 3 in ggplot2. Is there any trick to have a different spacing between the boxes of the legend? I also want to match the color of the legend box and the text. Currently, the legend text is black. So, I have two questions here

  1. How I add a different spacing between the legend keys: bigger spacing between box 2 and 3, keep the spacing between other legends key boxes similar).
  2. How to match the color of the legend key box with the text? Here is my data and code. I have also added the design of the graph I want to produce from this code.

Packages

library(tidyverse)
   library(ggplot2)
   library(ggtext)

Sample Data

Food = c("meat", "meat", "meat", "meat", "wheat","wheat","wheat", 
"wheat", "maize","maize","maize","maize")

 Subgroup = c("Male", "Female", "Urban", "Rural", "Male",  "Female", 
"Urban", "Rural",  "Male",  "Female","Urban", "Rural")

  mean = c(8.66, 10.45,  9.88,  7.32, 21.04, 19.65, 20.26, 20.87, 51.06 , 44.51,  47.60, 48.40)
df <- data.frame(Food, Subgroup,  mean)

df$Subgroup[df$Subgroup == "Urban"]  <- 1
df$Subgroup[df$Subgroup == "Rural"]  <- 2
df$Subgroup[df$Subgroup == "Female"] <- 3
df$Subgroup[df$Subgroup == "Male"]   <- 4

df$Subgroup <- factor(df$Subgroup,
                      levels = c(1, 2, 3, 4),
                      labels = c("Urban", "Rural", "Female", "Male"))



    #Color code
    
    
    colorPanel = c( '#42235e', '#7d103d', '#007da5', '#003b5d' )
    
    # bar chart
    
    
    Plot_FBGDS <-  ggplot(df, aes(x = Food, y = mean,  fill = Subgroup)) + 
      geom_col(stat = "identity", position = position_dodge(-0.84), width = 0.82) + 
      
      scale_y_continuous(breaks = c(0,20, 40, 60,80), expand = c(0,0),
                         limits = c(0,100), 
                         labels = function(x) paste0(x, "%")) + 
      geom_text(aes(label = paste0(mean,"%"), y = mean + 2, color = Subgroup), stat = "identity", 
                size = 3, vjust = 0.5, face = "bold", family = "sans",   position = position_dodge(-0.88)) +
      scale_color_manual(values = colorPanel) +
      scale_x_discrete(limits = c("meat",
                                  "wheat",
                                  "maize"))  + 
      
      coord_flip() +  
      scale_fill_manual(values =  colorPanel) +
                    
      labs( x= " ", 
            y = " ") +
      theme(text = element_text(size = 14, color = "black", family = "sans"),
            panel.background = element_rect(fill = "white"), 
            panel.border = element_blank(),
            axis.text.y = element_text(family = "sans", color = "black", size = 14),
            axis.text.x = element_blank(),
            axis.line.x =  element_blank(), 
            axis.line.y =  element_line(),
            axis.ticks.y.left = element_line(colour = "green"),
            axis.ticks.length=unit(0, "cm"),
            axis.title.x = element_text(size = 10, color = "black", family = "sans"),
            axis.text = element_text(size = 10, color = "black",family = "sans"),
            legend.key = element_rect(colour = NA, fill=NA, size= 7),
            legend.text = element_text(size = 10, family = "sans"),
            legend.margin=margin(t= -1, r= 2, b= 2, l= 2),
            legend.title =  element_blank(), 
            legend.key.height = unit(0.03, "npc"),
            legend.key.width = unit(0.03, "npc"),
            legend.position = c(0.85, 0.70),  
            panel.grid.major.x = element_blank(), 
            panel.grid.minor.y =  element_blank(),
            panel.grid.major.y = element_blank(),
            panel.grid.minor.x =  element_blank())
    
    
    
    
    Plot_FBGDS 

Current Plot

enter image description here

Shawn Hemelstrand
  • 2,676
  • 4
  • 17
  • 30
demoz ahn
  • 9
  • 3

1 Answers1

1

One option would be the ggnewscale package which allows to add a second fill legend. Doing so we could draw separate legends for Urban/Rural and for Male/Female for which the spacing could be set via legend.spacing.y and legend.margin. To make this work we have to add a duplicated geom_col where we have to explicitly map on the fill aes.

To color the legend text according to the bars you could make use of the ggtext package which allows styling of text via markdown, HTML and CSS. To this end use ggtext::element_markdown for legend.text and add labels to the legends where you set your desired colors using some HTML and CSS.

EDIT Following this answer by @teunbrand we could increase or set the spacing between legend keys via legend.spacing.y if we set byrow=TRUE in guide_legend. However, as legend.spacing.y also effects the spacing between legends you probably have to adjust the top and/or bottom legend.margin as well to get your desired spacing between the legends for each group.

library(ggplot2)
library(ggtext)

# Color code
colorPanel <- c("#42235e", "#7d103d", "#007da5", "#003b5d")
names(colorPanel) <- c("Urban", "Rural", "Female", "Male")

labels <- paste0("<span style='color:", colorPanel, ";'>", names(colorPanel), "</span>")
names(labels) <- names(colorPanel)
# bar chart

library(ggnewscale)

Plot_FBGDS <- ggplot(df, aes(x = Food, y = mean, fill = Subgroup)) +
  geom_col(position = position_dodge(-0.84), width = 0.82) +
  geom_text(aes(label = paste0(mean, "%"), y = mean + 1, color = Subgroup),
            size = 3, hjust = 0, vjust = 0.5, fontface = "bold", family = "sans", position = position_dodge(-0.88), show.legend = FALSE
  ) +
  scale_fill_manual(values = colorPanel, breaks = c("Urban", "Rural"), labels = labels[c("Urban", "Rural")],
                    aesthetics = c("color", "fill"),
                    guide = guide_legend(order = 1, byrow = TRUE)) +
  new_scale_fill() +
  new_scale_color() +
  geom_col(aes(fill = Subgroup), position = position_dodge(-0.84), width = 0.82) +
  scale_fill_manual(values = colorPanel, breaks = c("Female", "Male"), labels = labels[c("Female", "Male")], 
                    aesthetics = c("color", "fill"),
                    guide = guide_legend(order = 2, byrow = TRUE)) +
  scale_y_continuous(
    breaks = c(0, 20, 40, 60, 80), expand = c(0, 0),
    limits = c(0, 100),
    labels = function(x) paste0(x, "%")
  ) +
  scale_x_discrete(limits = c(
    "meat",
    "wheat",
    "maize"
  )) +
  coord_flip() +
  labs(x = " ", y = " ") +
  theme(
    text = element_text(size = 14, color = "black", family = "sans"),
    panel.background = element_rect(fill = "white"),
    panel.border = element_blank(),
    axis.text.y = element_text(family = "sans", color = "black", size = 14),
    axis.text.x = element_blank(),
    axis.line.x = element_blank(),
    axis.line.y = element_line(),
    axis.ticks.y.left = element_line(colour = "green"),
    axis.ticks.length = unit(0, "cm"),
    axis.title.x = element_text(size = 10, color = "black", family = "sans"),
    axis.text = element_text(size = 10, color = "black", family = "sans"),
    legend.text = element_markdown(size = 10, family = "sans"),
    legend.key = element_rect(colour = NA, fill = NA, size = 7),
    legend.key.height = unit(0.03, "npc"),
    legend.key.width = unit(0.03, "npc"),
    legend.margin = margin(t = 4, r = 2, b = 4, l = 2),
    legend.title = element_blank(),
    legend.position = c(0.85, 0.70),
    legend.spacing.y = unit(8, "pt"),
    panel.grid.major.x = element_blank(),
    panel.grid.minor.y = element_blank(),
    panel.grid.major.y = element_blank(),
    panel.grid.minor.x = element_blank()
  )

Plot_FBGDS
#> Warning: position_dodge requires non-overlapping x intervals

stefan
  • 90,330
  • 6
  • 25
  • 51
  • This code solves 95% of my issue. I wonder if there is a trick to increase the spacing between urban and rural as well as female and male. @stefan – demoz ahn Feb 01 '22 at 04:40
  • Hi @demozahn. Yep. There is. See my edit. – stefan Feb 01 '22 at 06:53
  • 1
    @tjebo. Absolutely fine with me. I agree that the same or similar solutions could be found in the duplicates you referenced. Kind of a multi-duplicate. :D BTW: You are quite amazing in finding duplicates. Much, much better than me. (: – stefan Feb 02 '22 at 07:47
  • haha :) I just made it my little mission to link as many threads to "super threads" as possible :D – tjebo Feb 02 '22 at 13:15