Emman
Emman

Reputation: 4201

Define a fixed width/height to a bar chart, then set an absolute width to bars, and absolute space between bars, in pixels

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.

Demonstration

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_all_bars

p_two_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).

all_7


2_bars

Bottom line

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

Answers (1)

Allan Cameron
Allan Cameron

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:

  1. Fix the size of the overall plot in pixels
  2. Fix the size of the plot panel as a proportion of the total plot width
  3. Automate the calculation of bar widths depending on the number of items on the x axis.

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

enter image description here


allbars.png

enter image description here


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

Related Questions