InColorado
InColorado

Reputation: 348

How to map geom_text color to a variable

For a bar graph, I want to stack the bar components vertically in a custom order.  In the example below, the variable that should control the stacking order is “mechanism.”  The intended stacking order is neither alphabetical order nor the order in which the dataframe’s rows are sequenced.  Not all bars have all mechanisms. I manually set the sequence and use the sequence as levels for a factor.  The entirely missing factor level doesn't get handled right, for which a solution would be welcome but I can work around that.

To add text labels indicating each component’s magnitude, I calculate the labels’ y height values and add a geom_text.  I want the color of the text to be a consistent contrast to the color of the bar component:  white text on blue bar components, etc.  So I assign geom_text's color to the same variable as the geom_bar’s fill.  But the intended color pairings for background and text do not remain matched.  Ideas please?? (thank you to the editor who inserted the picture of the messed up graph.)

The desired outcome is that each bar component whose mechanism is "hot" would have red fill annotated with black text. Each component whose mechanism is "urban" would have tan4 fill annotated with grey50 text.

I also tried assigning the color without making the mapped variable into a factor (example option 1), as in geom_text() & color gradient.

library(ggplot2)
m.data <- data.frame(m.model = factor(c(1, 1, 2, 2, 4)),
   mechanism = c("urban", "hot", "hot", "urban", "urban"),
   bar.height = 1:5,
   y.for.text = c(.5, 2, 1.5, 5, 2.5))
mechanism.order <- c("hot", "dry", "urban")
mechanism.colors <- c("red", "blue", "tan4")
text.colors <- c("black", "white", "grey50")

m.map <- ggplot() +
   geom_bar(data = m.data, aes(x = m.model, y = bar.height, 
      fill = factor(mechanism, levels = mechanism.order)), 
      stat = "identity") +
   scale_fill_manual(values = mechanism.colors) +
   scale_color_manual(values = text.colors) 

# option 1:
m.map +    
   geom_text(data = m.data, aes(x = m.model, y = y.for.text,
      color = mechanism, label = bar.height)) 
   
# option 2:
m.map +    
   geom_text(data = m.data, aes(x = m.model, y = y.for.text,
      color = factor(mechanism, levels = mechanism.order), 
      label = bar.height))

Upvotes: 2

Views: 2022

Answers (3)

TarJae
TarJae

Reputation: 78907

Suggestion with modified data. Most of the procedure already presented by dear @Alan Cameron. New is to transform to factor before ggplot:

m.data <- data.frame(m.model = factor(c(1, 1, 2, 2, 4)),
                     mechanism = c("urban", "hot", "dry", "urban", "urban"),
                     bar.height = 1:5,
                     y.for.text = c(.5, 2, 1.5, 5, 2.5))
mechanism.order <- c("hot", "dry", "urban")
mechanism.colors <- c("red", "blue", "tan4")
text.colors <- c("black", "white", "grey50")
library(tidyverse)

m.data %>% 
  mutate(mechanism = factor(mechanism, levels = mechanism.order)) %>% 
  ggplot(aes(x=m.model, y=bar.height, fill=mechanism))+
  geom_col()+
  geom_text(aes(label = bar.height, color = mechanism),
            position = position_stack(vjust = 0.5)) +
  scale_fill_manual(values = mechanism.colors) +
  scale_color_manual(values = text.colors) 

enter image description here

Upvotes: 1

Allan Cameron
Allan Cameron

Reputation: 173793

To be explicit about the colours, pass them as a named vector.

A couple of other points here:

  1. A geom_bar with stat = "identity" is just a long way of writing geom_col
  2. You don't need to hand code y positions for text. Just use position_stack(vjust = 0.5) inside geom_text to have central bar labels
  3. If your layers all use the same data and x, y variables, pass them via the initial ggplot call and let the layers inherit them
mechanism.colors <- c(hot = "red", dry = "blue", urban = "tan4")
text.colors <- c(hot = "black", dry = "white", urban = "gray50")

ggplot(data = m.data, aes(x = m.model, y = bar.height)) +
   geom_col(aes( fill = factor(mechanism, levels = mechanism.order))) +
   geom_text(aes(label = bar.height, color = mechanism),
             position = position_stack(vjust = 0.5), size = 8) +
   scale_fill_manual(values = mechanism.colors) +
   scale_color_manual(values = text.colors) +
   labs(fill = "mechanism") +
   guides(color = guide_none())

enter image description here

Upvotes: 2

Gnueghoidune
Gnueghoidune

Reputation: 1357

I'm not sure I understand your desired output, but maybe this helps:

I slightly updated your ggplot call: if you provide the aes() in the initial ggplot call, you don't need to repeat afterwards. Furthermore, there is no need to add factor() to the fill = statement. In geom_text, put color = mechanism into aes(), so you can access it afterwards with scale_color_manual to add your custom colors. Lastly, you can add position = position_stack to get the text in the middle.

ggplot(m.data, aes(m.model, bar.height, fill = mechanism)) +
  geom_bar(stat = "identity") +
  scale_fill_manual(values = mechanism.colors) +
  geom_text(aes(label = bar.height, color = mechanism), position = position_stack(.5)) +
  scale_color_manual(values = c("black", "grey50 "))

Output:

enter image description here

However, as you can see, gray40 might not be the best color on a blue background, since the contrast is not very good. You might want to choose another color.

Upvotes: 3

Related Questions