I’m trying to plot overlapping stacked bar charts with the same aestetics and different widths of the bars.
My current solution is by adding separate geom_cols, but the same aestetics. Just different filters on the data set. But this gives the same fill colours two both. I can change this by adding alpha to one of them, which looks OK for me.
The problem here is then, that I still get only one legend for both bars as you can see in the following example:
library(tidyverse)
data <- tribble(
~status , ~category , ~month , ~value ,
'plan' , 'A' , 'Jan' , 5000 ,
'plan' , 'A' , 'Feb' , 4900 ,
'plan' , 'A' , 'Mar' , 4900 ,
'plan' , 'B' , 'Jan' , 0 ,
'plan' , 'B' , 'Feb' , 499 ,
'plan' , 'B' , 'Mar' , 600 ,
'actual' , 'A' , 'Jan' , 5200 ,
'actual' , 'A' , 'Feb' , 4950 ,
'actual' , 'A' , 'Mar' , 4700 ,
'actual' , 'B' , 'Jan' , 0 ,
'actual' , 'B' , 'Feb' , 380 ,
'actual' , 'B' , 'Mar' , 400
) |>
mutate(month = factor(month , levels = month.abb[1:3]))
data |>
ggplot(mapping = ggplot2::aes(x = month , y = value)) +
geom_col(data = ~ . |> filter(status == 'plan') ,
mapping = ggplot2::aes(fill = category) ,
alpha = 0.5 ,
position = position_stack(reverse = TRUE)) +
geom_col(data = ~ . |> filter(status == 'actual') ,
mapping = aes(fill = category) ,
position = position_stack(reverse = TRUE) ,
width = 0.5) +
scale_fill_manual(name = 'Category' ,
values = c('A' = '#628395' , 'B' = '#95A472') ,
labels = c('A' = 'one' , 'B' = 'two'))
I can put alpha in the aes() of both bars and add a scale_alpha_manual, this gives an additional legend, but with wrong colours.
So, does anyone have an idea, how I can achieve a similar plot with two legends?
>Solution :
You can use fill=
on an "interaction" of status and category.
data |>
ggplot(mapping = ggplot2::aes(x = month , y = value)) +
geom_col(data = ~ . |> filter(status == 'plan') ,
mapping = ggplot2::aes(fill = interaction(status, category)) ,
position = position_stack(reverse = TRUE)) +
geom_col(data = ~ . |> filter(status == 'actual') ,
mapping = aes(fill = interaction(status, category)) ,
position = position_stack(reverse = TRUE) ,
width = 0.5) +
scale_fill_manual(name = 'Category' ,
values = c('actual.A' = '#628395' , 'actual.B' = '#95A472',
'plan.A' = '#62839588' , 'plan.B' = '#95A47288') ,
labels = c('A' = 'one' , 'B' = 'two'))
The labels actual.A
and such may not be inspiring. All interaction
is doing is creating a concatenated string collapsed with .
, so you can create your own new field that combines category and status in a way that you prefer.