Reputation: 4201
I want to generate bar charts using ggplot2
that will follow a strict standard:
I work with RStudio, that allows responsiveness in its viewer. That means that when I expand the viewer's boundaries, the plot is stretched accordingly, increasing bars' width and spaces between them. Conversely, making the viewer's boundaries smaller would make the bars thinner and reduce space between them.
Similarly, in given boundaries of the viewer, plotting a bar chart would yield different bar width for 6 bars than when there are 2 bars only.
library(ggplot2)
library(dplyr)
p_all_bars <-
mpg %>%
ggplot(aes(x = class)) +
geom_bar()
p_two_bars <-
mpg %>%
filter(class == "compact" | class == "suv") %>%
ggplot(aes(x = class)) +
geom_bar()
p_all_bars
p_two_bars
If I save both plots with dimensions of width = 1000 pixels
and height = 650 pixels
it's clear that both bar width and space between bars are different from one plot (7 bars) to another (2 bars).
How can I set absolute values for plot's height and width, in pixels, as well for bars' width and space between bars, in pixels too -- regardless of number of bars in the plot?
Upvotes: 5
Views: 2710
Reputation: 173858
The ability of ggplot2 to scale nicely to fit differently sized viewports is one of its greatest assets for most use cases. However, it makes life more difficult in a situation like this, where you actually want fixed sizes of objects within the main plot panel. The reason for this is that the panel size itself is not fixed. Rather, it grows and shrinks according to the device size, thereby allowing certain other elements of the plot (such as axis elements, text and titles) to remain fixed in size.
It is possible, but it's not easy. To do it you will need to:
If you think this all sounds like too much trouble, then I would probably agree. It's worth doing if you want to produce many plots. For the odd one here or there, I would probably add dummy factor levels along the x axis to keep the plot consistent, then use imaging software to cut out the extra space on the axis.
However, it is possible, so for what it's worth, here's an example of making the bars fixed at 51 pixels wide with 6 pixel gaps using programmatic methods:
library(ggplot2)
library(dplyr)
p_all_bars <-
mpg %>%
ggplot(aes(x = class)) +
geom_bar() +
scale_x_discrete(expand = expansion(0, 1/7))
p_two_bars <-
mpg %>%
filter(class == "compact" | class == "suv") %>%
ggplot(aes(x = class)) +
geom_bar() +
scale_x_discrete(expand = expansion(mult = 0, 1/2))
two_bars <- ggplot_gtable(ggplot_build(p_two_bars))
all_bars <- ggplot_gtable(ggplot_build(p_all_bars))
all_bars$widths[5] <- unit(14, "cm")
all_bars$widths[1] <- unit(1, "null")
two_bars$widths[5] <- unit(4, "cm")
two_bars$widths[1] <- unit(1, "null")
png("twobars.png", width = 500, height = 500)
plot(two_bars)
dev.off()
png("allbars.png", width = 500, height = 500)
plot(all_bars)
dev.off()
twobars.png
allbars.png
EDIT
An alternative is to keep the panel at a fixed width and use coord_cartesian
to keep the bar widths constant.
Take the following function:
library(ggplot2)
library(dplyr)
fixed_bars <- function(data, var, ncols = 6) {
ncols <- ncols + 2
data <- mutate(data, {{var}} := as.factor({{var}}))
levs <- length(levels(pull(data, {{var}})))
extra <- ncols - levs
x_range <- range(as.numeric(pull(data, {{var}}))) + c(-0.5, 0.5) * extra
ggplot(data, aes(x = {{var}})) +
geom_bar() +
coord_cartesian(xlim = x_range)
}
Which allows the folliwing behaviour:
mpg %>%
fixed_bars(class)
mpg %>%
filter(class == "compact" | class == "suv") %>%
fixed_bars(class)
Created on 2020-12-24 by the reprex package (v0.3.0)
Upvotes: 3