Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Fill a boxplot with 2 colors

Tags:

r

ggplot2

I would like to fill a boxplot with 2 colors instead of 1 in ggplot(), using geom_boxplot().

For example, there would be a first color between 0% and 80% of the boxplot width, and a second color between 80% and 100% of the boxplot width.

I could not find other example of such plots with geom_boxplot so I am not sure this is feasible.

I would like to avoid using geom_rect() or annotate().

Reproducible example

##### Initiating objects
### Colors
colFill <- c("#4a7ff9", "#4aecf9")
colShader <- c("#4964a3", "#499ba2")

### Dataset
set.seed(1)
df <- data.frame(Values=rnorm(100, 100, 10), 
                 Group=rep(c("Blue", "Cyan"), each=50))

##### Display plot
ggplot(df, aes(y=Values, x=Group, fill=Group)) + 
  geom_boxplot(lwd=3, width=0.5) + 
  scale_fill_manual(values=colFill) + 
  theme_classic() + 
  theme(legend.position="none")

enter image description here

My aim

enter image description here

Thank you

like image 681
Yacine Hajji Avatar asked May 15 '26 09:05

Yacine Hajji


2 Answers

Here's a way to do it using only geom_boxplot. We use three layers: the first is main fill color only, with no lines. The second is a thin boxplot filled with the shading color, with no lines or outliers, nudged to the right. The third is a line-only boxplot. This avoids the need for using annotations or grob hacking.

ggplot(df, aes(y = Values, x = Group, fill = Group)) + 
  geom_boxplot(lwd = 0, width = 0.5) + 
  geom_boxplot(position = position_nudge(0.2), width = 0.1,
               lwd = 0, aes(fill = paste(Group, 2)), outlier.shape = NA) +
  geom_boxplot(lwd = 3, width = 0.5, fill = NA) + 
  scale_fill_manual(values = c(colFill, colShader)[c(1, 3, 2, 4)], 
                    guide = 'none') + 
  theme_classic() 

enter image description here

like image 87
Allan Cameron Avatar answered May 17 '26 18:05

Allan Cameron


This is a chance to play around with the patterns and gradients in ggplot 3.5.0. Here are two approaches:

Using grid::rectGrob()

Create a function that returns a list of two rectGrob objects of different colors, which will act as the pattern:

library(grid)
create_pattern <- function(color1, color2) {
    gList(
        rectGrob(
            x = 1, y = 1, width = 1, height = 2, hjust = 1,
            gp = gpar(fill = color1, col = NA)
        ),
        rectGrob(
            x = 1, y = 1, width = 0.25, height = 2, hjust = 1,
            gp = gpar(fill = color2, col = NA)
        )
    ) |>
        gTree(children = _) |>
        pattern()
}

Then we can create the patterns and draw the plot:

patterns <- list(
    create_pattern(colFill[1], colShader[1]),
    create_pattern(colFill[2], colShader[2])
)

# or if you have more levels
# patterns <- Map(\(x, y) create_pattern(x, y), colFill, colShader, USE.NAMES = FALSE)

ggplot(df, aes(y = Values, x = Group, fill = Group)) +
    geom_boxplot(lwd = 3, width = 0.5) +
    scale_fill_manual(values = patterns) +
    theme_classic() +
    theme(legend.position = "none")

enter image description here

Using grid::linearGradient()

A more elegant solution using linearGradient(), suggested by Allan Cameron. Again we can create a function to produce the gradient:

binary_gradient <- function(color1, color2, ratio = c(3, 1)) {
    linearGradient(
        c(color1, color1, color2, color2),
        stops = c(c(0, ratio[1] / sum(ratio), ratio[1] / sum(ratio), 1)),
        y1 = 1
    )
}
patterns <- Map(
    binary_gradient,
    colFill,
    colShader,
    USE.NAMES = FALSE
)

ggplot(df, aes(y = Values, x = Group, fill = Group)) +
    geom_boxplot(lwd = 3, width = 0.5) +
    scale_fill_manual(values = patterns) +
    theme_classic() +
    theme(legend.position = "none")
# ^^ produces the same plot as above
like image 20
SamR Avatar answered May 17 '26 20:05

SamR



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!