How to color the border of geom_col in ggplot2 to avoid overlap?

Viewed 1519

I want to color the border of the bar graph in ggplot2.

The following script is an example.

As you can see, the orange border overlaps with the blue border. Is there any way to avoid this behavior and color the border within the graph?


library(tidyverse)
dat <- tibble(
  dx = c("D+","D+","D-","D-"),
  test    = c("T+","T-","T+","T-"),
  num     = c(40,80,100,800)
)

ggplot(dat) +
  geom_col(aes(x = dx, y = num, fill = dx, color = test),
           size = 3) +
  scale_color_manual(values = c("orange","blue"))

enter image description here

3 Answers

The problem is that bar plots are built out of grid::rectGrob, and when you make the outline of a rectGrob larger, it grows out the way. Since lines are a fixed point size, but the bars themselves aren't (as you'll see if you change the window size), there's no easy way to just shrink the rectGrobs to compensate for this to allow for internal outlining. So this is effectively a harder problem to fix than it first appears. Of course, it's not impossible, but your three options are:

  1. Choose a different way to plot (like position_dodge)
  2. Achieve the effect you are looking for with a temporary hack
  3. Write a whole new geom to achieve the effect (or find a package that has already done this)

If this is just a one-off and you are keen to pursue a particular look for your plot, I would definitely go for option 2. Here's an example of how it could be achieved:

ggplot(dat) +
  geom_col(aes(x = dx, y = num, fill = dx, color = test),
           size = 3) +
  scale_color_manual(values = c("orange","blue")) +
  geom_segment(aes(x = 0.53, y = 100, xend = 1.465, yend = 100), 
               size = 3, colour = "blue") +
  geom_segment(aes(x = 0.53, y = 120, xend = 1.465, yend = 120), 
               size = 3, colour = "orange") +
  geom_segment(aes(x = 1.53, y = 40, xend = 2.465, yend = 40), 
               size = 3, colour = "blue") +
  geom_segment(aes(x = 1.53, y = 60, xend = 2.465, yend = 60), 
               size = 3, colour = "orange") 

enter image description here

Since you've already got dx on the x-axis, there's no need to colour or fill it.

ggplot(dat) +
  geom_col(aes(x = dx, y = num, fill = test))

enter image description here

A hacky solution to your problem could be to plot a bar over the existing one. The new layer then only contains one test group and the values of this group are reduced a little bit. Just enough that the border won't overlap.

dat %>%
  ggplot2::ggplot(ggplot2::aes(x=dx,y=num,fill=dx,color=test)) +
  ggplot2::geom_bar(stat="identity",size=1) +
  ggplot2::geom_bar(dat=dat %>% 
                      dplyr::filter(test=="T+") %>%
                      dplyr::mutate(num2=num-5),
                    ggplot2::aes(y=num2),stat="identity",size=1) +
  ggplot2::scale_color_manual(values=c("blue","orange"))

The cleaner way would probably be to dodge the bars and then use position_dodge (I don't know an equivalent to this for stacked bars)

dat %>%
  ggplot2::ggplot(ggplot2::aes(x=dx,y=num,fill=dx,color=test)) +
  ggplot2::geom_col(size=1,position=ggplot2::position_dodge(0.93)) +
  ggplot2::scale_color_manual(values=c("blue","orange"))
Related