4

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

ironwest
  • 187
  • 2
  • 9
  • [This](https://stackoverflow.com/questions/29981027/is-it-possible-to-put-space-between-stacks-in-ggplot2-stacked-bar) question is very related but the trick is to use the bar borders this is not really what you want... – Paul Jun 19 '20 at 08:10
  • And [this](https://stackoverflow.com/questions/50925747/add-spacing-between-groups-in-a-stacked-bar-plot) more recent one uses the same trick – Paul Jun 19 '20 at 08:14
  • 1
    Without sounding like a preacher, the bars in a barchart should not have any borders. The base R default actually includes them, and although they are thin, including them at all goes against one of Edward Tufte's many rules of data visualization. – Edward Jun 19 '20 at 08:50
  • @Paul. Thank you for mentioning the other questions. I found tricks used in those questions are also very helpful. – ironwest Jun 19 '20 at 14:04
  • @Edward. Thank you for your comment on data visualization rules. I will look up the rules to see if my visualization suits the use case or not. – ironwest Jun 19 '20 at 14:08

3 Answers3

3

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

Allan Cameron
  • 147,086
  • 7
  • 49
  • 87
  • Thank you very much for the answer and fine-tunings of position in the `geom_segment`s' aesthetics. It will be great if I had the skill to go for 3, but unfortunately, I don't, so, I think your answer is best for this question. – ironwest Jun 19 '20 at 13:51
1

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

Edward
  • 10,360
  • 2
  • 11
  • 26
  • 1
    still it is interesting to see how to "space" categories in stacked geom_col. – Paul Jun 19 '20 at 07:41
  • 1
    @ Paul. Perhaps `geom_mosaic`? – Edward Jun 19 '20 at 07:49
  • @Edward. Thank you for the comment. I want to show two bar graphs to present how Sensitivity and Specificity of the test will affect the pre- and post- disease probability. So, I tried to wrap the area of the graph with a thick border. I looked up for the ggmosaic package, which you recommend and find it very helpful to use in my situation. – ironwest Jun 19 '20 at 14:02
0

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"))
sambold
  • 807
  • 5
  • 15
  • Thank you for your answer! Your first solution actually works well with line size 1, but with larger value in size, there is still an overlap. But great with no need of fine-tuning the position of lines. – ironwest Jun 19 '20 at 13:58
  • if your line size is greater than 1, you 'just' have to adjust the value that you subtract from num ... trial and error :D – sambold Jun 19 '20 at 15:48