1

I'm having trouble, and I don't know exactly how to explain, so sorry in advance.

I have a script that I'll not put here since it is very large, so let's go directly to the problem.

Since the script generates a logo which is a kind of histogram I'll try to simulate the problem with an example.

I'm using a function to split the histogram according to the number of bars, in the end, it generates a plot with various grobs each on containing a piece of the histogram.

myranges <- function(start, end, step){
  starts <- seq(start, end, step+1)
  ends <- pmin(starts + step, end)
  data.frame(a = starts, b = ends)
}

d <- data.frame(a = letters[1:10] , b = seq(1, 20, by =2 ))


num_bar_per_hist <- 2

p_list <- apply(
  myranges(1, nrow(d), num_bar_per_hist - 1),
  1,
  function(x){
    #as.matrix(d[x[1]:x[2]],)
    ggplot(d[x[1]:x[2] , ], aes(x=a, y=b)) + 
      geom_bar(stat = "identity") +
      ylim(0,22) +
      theme_classic()
  }
)
do.call(gridExtra::grid.arrange, c(p_list, ncol=2))

Above, the piece of the script which generates the plot.
I change the var num_bar_per_hist to split the histogram. In this example I got this:

enter image description here

If I change the value of num_bar_per_hist to 5 I got:

enter image description here

These two examples show that the width of each plot keeps the same, also the width of the bars.
But, if I change the num_bar_per_hist to 3 I got:

enter image description here

What I'd like is keep the width of the bars with the same size. That means, the bar j should be aligned and with the same width of the bar d which are right above.

Also, if possible, I'd like not to change the structure of the script (too much), or easy/small changes.

I'm grateful in advance. Thanks

EDIT:

Using cowplot I could solve part of the problem. (same parameter of 3rd plot)

num_bar_per_hist <- 3

p_list <- apply(
  myranges(1, nrow(d), num_bar_per_hist - 1),
  1,
  function(x){
    ggdraw() +
      draw_plot(
        ggplot(d[x[1]:x[2] , ], aes(x=a, y=b)) + 
          geom_bar(stat = "identity") +
          ylim(0, 22),
        width = (x[2] - x[1] + 1) / num_bar_per_hist
      )
  }
)
do.call(gridExtra::grid.arrange, c(p_list, ncol=2))

This way I got:

enter image description here

This way I'm getting close to the solution. The j still has not the same width than the others but is too better than the 3rd plot.
I'm still needing some trick or "magic number" to improve the width of the bar. Because of that strategy lead the last grob became thinner than the others. And I just want to keep all bars with the same width independently of the difference of the number of bars each plot has.

Aureliano Guedes
  • 767
  • 6
  • 22
  • Can you describe more of what you want the output to be when `num_bar_per_hist` is 3? – Brandon May 04 '19 at 04:42
  • @Brandon I just want all bars with the same width. – Aureliano Guedes May 04 '19 at 14:32
  • That means, in my example, the bar `j` should have the same width of bar `d` which are right above. – Aureliano Guedes May 04 '19 at 15:41
  • I think I may use `cowplot` instead `gridExtra` but I dont know how to call it with `do.call` – Aureliano Guedes May 04 '19 at 18:39
  • 1
    https://stackoverflow.com/questions/30196143/bars-in-geom-bar-have-unwanted-different-widths-when-using-facet-wrap/30201770 perhaps gives a partial way - to generate a width in the plot loop. *rough code* : `s <- split(d, rep(seq_len(nrow(d)), each=num_bar_per_hist, length=nrow(d))) ; l = lapply(s, function(x) { x$wd = nrow(x) / num_bar_per_hist ; ggplot(x, aes(x=a, y=b, width=0.5*wd)) + geom_bar(stat = "identity") + ylim(0,22) + theme_classic() }) ; grid.arrange(grobs=l, ncol=2)` – user20650 May 06 '19 at 16:24
  • @user20650 thanks. But there is some way to keep left aligned? – Aureliano Guedes May 06 '19 at 18:56
  • there may be a way within ggplot, but a dirty way is to append unused factor levels. This actually takes care of the widths as well. Again some quick code: `l = lapply(s, function(x) { x = droplevels(x); keep = levels(x$a); needed = num_bar_per_hist - nlevels(x$a); if( needed ) x$a = factor(x$a, levels=c(levels(x$a), paste("", 1:needed))); ggplot(x, aes(x=a, y=b)) + geom_bar(stat = "identity") + ylim(0,22) + theme_classic() + if(needed) scale_x_discrete(drop=FALSE, labels=c(keep, rep("", needed))) })` – user20650 May 06 '19 at 19:59

1 Answers1

1

you can dig out the number of breaks, and from there modify each plot to ensure they all have as many breaks by adding dummy ones to the x scale,

how_many <- function(p){
  gb <- ggplot_build(p)
  length(gb$layout$panel_params[[1]][['x.major']])
}

raxe <- function(p, n){

  gb <- ggplot_build(p)
  x_params <- gb$layout$panel_params[[1]]
  ni <- length(x_params[['x.major']])
  labels <- x_params[['x.labels']]
  if(ni < n){
    dummy <- c(labels, paste0("__",letters[seq_len(n-ni)]))
    print(dummy)
    phantom <- c(labels, rep('', n-ni))
    return(p + scale_x_discrete(lim=dummy, labels=phantom))
  }
p
}

n_breaks <- sapply(p_list, how_many)
p_list <- lapply(p_list, raxe, max(n_breaks))

egg::ggarrange(plots = p_list, ncol=2)
  • To this example it works, I think you deserve the win bounty. But in the case, I solved in another way using cowplot. Capable to handle with cases where the bar is changed by a letter. Thanks a lot – Aureliano Guedes May 07 '19 at 20:31