Bushroot
Bushroot

Reputation: 269

Is there an efficient way to draw lines between different elements in a stacked bar plot using ggplot2?

I would like to draw lines between different elements in a stacked bar plot using ggplot2. I have plotted a stacked barchart using ggplot2 (first figure), but would like to get something like in second figure.

dta <- tribble(
  ~colA, ~colB, ~colC,
  "A",   "a",   1,
  "A",   "b",   3,
  "B",   "a",   4,
  "B",   "b",   2); dta

ggplot(dta, aes(x = colA, y = colC, fill = colB)) + 
  geom_bar(stat = "identity")

Fig. 1 Fig. 2

The fastes way would probably to the add the lines by manually drawing them into the exported image. However, I prefere avoiding this.

This Stackoverflow entry (esp. the answere of Henrik) gives a potential solution. However, I was wondering whether there is another solution that is more generic (i.e. that does not require to manually define all the start and end points of the segments/lines)

Upvotes: 2

Views: 1393

Answers (2)

Nicolas Payette
Nicolas Payette

Reputation: 14972

You can achieve this by combining geom_col() with a stacked geom_area(). The trick is to tweak column justification such that the first column is left of centre and the second one is to the right:

library(ggplot2)
library(tibble)
dta <- tribble(
  ~colA, ~colB, ~colC,
  "A",   "a",   1,
  "A",   "b",   3,
  "B",   "a",   4,
  "B",   "b",   2
)
dta %>% 
  ggplot(aes(x = colA, y = colC)) +
  geom_col(aes(fill = colB), just = c(1, 1, 0, 0)) +
  geom_area(aes(group = colB), colour = "black", fill = NA)

Created on 2023-11-25 with reprex v2.0.2

Upvotes: 1

aosmith
aosmith

Reputation: 36086

You could use the "factor as numbers" trick to draw lines between the bar centers (shown, e.g., here).

In your case this needs to be combined with stacking in geom_line().

ggplot(dta, aes(x = colA, y = colC, fill = colB)) + 
    geom_bar(stat = "identity") +
    geom_line( aes(x = as.numeric(factor(colA))), 
               position = position_stack())

enter image description here Getting the lines to the edges instead of the center would take some manual work. It's OK if you really only have two stacks like this, but would be difficult to easily scale.

In this case you'd want to add .45 to the group that comes first on the x axis and subtract .45 from the second. This might seem magical, but the default width is 90% of the resolution of the data so I used half of 0.9.

dta = transform(dta, colA_num = ifelse(colA == "A",
                                    as.numeric(factor(colA)) + .45,
                                    as.numeric(factor(colA)) - .45) )


ggplot(dta, aes(x = colA, y = colC, fill = colB)) + 
    geom_bar(stat = "identity") +
    geom_line( aes(x = colA_num), 
               position = position_stack())

enter image description here

This doesn't add a line at 0 because those values aren't in the dataset. This could be added as a segment along the lines of

annotate(geom = "segment", y = 0, yend = 0, x = 1.45, xend = 1.55)

Upvotes: 5

Related Questions