I'm trying to have a ggplot with two vertical axes to be produced in different ways depending on user selection. One selection lets the user reverse each axis. Another selection lets them select between points and bars for the series in each axis. I'm having issues when the main (left-side) y axis is reversed and the secondary (right-side) data is set to be displayed as bars. In that case, the bars start from the top of the plot instead of the bottom as they should based on the secondary axis orientation.
With points at both sides everything seems good:
Poitns don't have a bottom or top, but just a coordinate, thus they look good. But with bars at the right side, as soon as I reverse the left-side y axis, the bars are flipped vertically starting at the top and growing downwards until reaching the corresponding coordinate:
(please ignore the legend symbols, I can correct that myself but would extend the code of the MRE).
For what I've learnt from other posts (e.g., [A], [B], ) ggplot doesn't let one handle two totally independent vertical axes, but just a secondary one which is a rescaling of the main one. And that is the root of the problem I'm having. My bars are taking the left-side zero as their min y value.
I would only want that to happen if I also selected to reverse the secondary axis, which is not the case here. If only the left-side y axis is reversed, I would expect the right side bars start from the bottom of the plot, which is the zero of the right side.
MRE
** Data
df1 <- data.frame(ID = c("A", "A", "A", "A", "B", "B", "B", "B"),
Date = structure(c(19078, 19085, 19092, 19099, 19078, 19085, 19092, 19099), class = "Date"),
Val = c(236, 221, 187, 136, 77, 100, 128, 180))
df2 <- data.frame(ID = c("J", "J", "J", "J", "K", "K", "K", "K"),
Date = structure(c(19078, 19085, 19092, 19099, 19078, 19085, 19092, 19099), class = "Date"),
Val = c(478, 500, 549, 479, 73, 5, 15, 74))
** Working case with points at both sides
library(dplyr)
library(ggplot2)
# prepare y2 scaled data
ylim1 <- rev(range(df1$Val))
ylim2 <- range(df2$Val)
scale_y2.1 <- function(y, ylim1, ylim2) {
ylim1[1] + (ylim1[2] - ylim1[1]) *(y - ylim2[1])/(ylim2[2] - ylim2[1])
}
dfAll <- full_join(df1, df2, by = c("ID", "Date"), suffix = c("1", "2"))
y2.scl <- scale_y2.1(dfAll$Val2, ylim1, ylim2)
dfAll <- dfAll %>% mutate(Val2_scl = y2.scl)
# prepare y2 ticks and scaled breaks
labs2 <- pretty(ylim2)
brks2 <- scale_y2.1(labs2, ylim1, ylim2)
# generate plot
ggplot(dfAll) +
geom_point(aes(x = Date, y = Val1, color = ID, group = ID), na.rm = TRUE) +
geom_point(aes(x = Date, y = Val2_scl, color = ID, group = ID), na.rm = TRUE) +
scale_y_continuous(trans = "reverse", sec.axis = dup_axis(breaks = rev(brks2),
labels = rev(labs2),
name = "Val2"))
** Broken case with bars at right side
# generate plot
ggplot(dfAll) +
geom_point(aes(x = Date, y = Val1, color = ID, group = ID), na.rm = TRUE) +
geom_bar(aes(x = Date, y = Val2_scl, fill = ID, group = ID), na.rm = TRUE,
stat = "identity", position = position_dodge(preserve = "single")) +
scale_y_continuous(trans = "reverse", sec.axis = dup_axis(breaks = rev(brks2),
labels = rev(labs2),
name = "Val2"))
** This SO answer seems to have some potential of working for my case. It has a reversed left-side axis and still the bars grow from the bottom up. Unfortunatelly, the answer didn't have any explanation of what is done or why it works. It is just the working code. I wasn't able to adjust it to work for me.
** This other SO answer could also work since geom_linerange()
lets one specify min and max y values. However, in that case it is necessary to manually set the width of the lineranges to make them look as bars. In my setting, the number of series to plot can vary from just a couple to more than 20. Thus, setting the bar width by hand doesn't seem very robust/practical.
We could use geom_tile
:
ggplot(dfAll) +
geom_point(aes(x = Date, y = Val1, color = ID, group = ID), na.rm = TRUE) +
geom_tile(aes(x = Date, y = (ylim1[1] + Val2_scl)/2, height = ylim1[1]-Val2_scl,
fill = ID, group = ID), na.rm = TRUE,
stat = "identity", position = position_dodge(preserve = "single")) +
scale_y_continuous(trans = "reverse", sec.axis = dup_axis(breaks = rev(brks2),
labels = rev(labs2),
name = "Val2"))