tidyverse / ggplot2

An implementation of the Grammar of Graphics in R
https://ggplot2.tidyverse.org
Other
6.5k stars 2.02k forks source link

Incorrect tick positions of the secondary axis #3576

Closed Ilia-Kosenkov closed 5 years ago

Ilia-Kosenkov commented 5 years ago

While building a derivative of facet_wrap, I stacked several plots, produced from mtcars data set, and noticed, that, with sec.axis set, top axis ticks of the bottom plot do not exactly align with the bottom ticks of the top plot. (See figures below).

1

And a zoomed-in version.

2

The issue does not seem to be related to any of my modifications and can be easily reproduced in base ggplot2. In the reprex I build a simple plot with a secondary x axis, then check grobs of each axis and retrieve the x coord of polyLine grob. The difference, while small, is clearly seen.


library(ggplot2, quietly = TRUE, warn.conflicts = FALSE)
library(dplyr, quietly = TRUE, warn.conflicts = FALSE)
library(grid, quietly = TRUE, warn.conflicts = FALSE)

# Simple plot with secondary, duplicated, x-axis
ggplot(mtcars, aes(hp, mpg)) +
    geom_point() +
    scale_x_continuous(sec.axis = dup_axis()) -> plt

# Converting to gtable to get each grob
plt %>% ggplot_build %>% ggplot_gtable -> tbl
tbl$layout %>% mutate(rowid = 1:n()) -> layout
# Indices of grobs
top_ind <- layout %>% filter(name == "axis-t") %>% pull(rowid)
bot_ind <- layout %>% filter(name == "axis-b") %>% pull(rowid)

# Axis grob is nested, so go deep until we hit the `polyLine` and its `x` coordinates
tbl$grobs[[top_ind]]$children[[2]]$grobs[[2]]$x -> top_tick_pos
# For another axis, slightly different order
tbl$grobs[[bot_ind]]$children[[2]]$grobs[[1]]$x -> bot_tick_pos
# Compare
print(top_tick_pos)
#> [1] 0.199199199199199native 0.199199199199199native 0.52052052052052native 
#> [4] 0.52052052052052native  0.841841841841842native 0.841841841841842native
print(bot_tick_pos)
#> [1] 0.199646643109541native 0.199646643109541native 0.520880179890781native
#> [4] 0.520880179890781native 0.842113716672021native 0.842113716672021native
diff <- convertX(bot_tick_pos, "native", TRUE) - convertX(top_tick_pos, "native", TRUE)

avg_diff <- sqrt(sum(diff^2) / length(diff))
print(avg_diff)
#> [1] 0.0003667319

Created on 2019-10-18 by the reprex package (v0.3.0)

This is reproduced in 115c3960d0fd068f1ca4cfe4650c0e0474aabba5.

clauswilke commented 5 years ago

I can confirm this problem exists in the current ggplot2 master.

@paleolimbot, since you have recently rewritten all this code, do you have any idea what could cause this problem?

paleolimbot commented 5 years ago

I think this has been the case for a long time. I definitely changed some things about the secondary axis, but I didn't change that transforms are discretized using a 1000-point approximation:

https://github.com/tidyverse/ggplot2/blob/115c3960d0fd068f1ca4cfe4650c0e0474aabba5/R/axis-secondary.R#L139-L142

Currently you can't change the level of detail, however in the new version, you can specify a second guide axis that circumvents the sec_axis() framework:

library(ggplot2, quietly = TRUE, warn.conflicts = FALSE)
library(dplyr, quietly = TRUE, warn.conflicts = FALSE)
library(grid, quietly = TRUE, warn.conflicts = FALSE)

# Simple plot with secondary, duplicated, x-axis
ggplot(mtcars, aes(hp, mpg)) +
  geom_point() +
  guides(x.sec = guide_axis()) -> plt

# Converting to gtable to get each grob
plt %>% ggplot_build %>% ggplot_gtable -> tbl
tbl$layout %>% mutate(rowid = 1:n()) -> layout
# Indices of grobs
top_ind <- layout %>% filter(name == "axis-t") %>% pull(rowid)
bot_ind <- layout %>% filter(name == "axis-b") %>% pull(rowid)

# Axis grob is nested, so go deep until we hit the `polyLine` and its `x` coordinates
tbl$grobs[[top_ind]]$children[[2]]$grobs[[2]]$x -> top_tick_pos
# For another axis, slightly different order
tbl$grobs[[bot_ind]]$children[[2]]$grobs[[1]]$x -> bot_tick_pos
# Compare
print(top_tick_pos)
#> [1] 0.199646643109541native 0.199646643109541native 0.520880179890781native
#> [4] 0.520880179890781native 0.842113716672021native 0.842113716672021native
print(bot_tick_pos)
#> [1] 0.199646643109541native 0.199646643109541native 0.520880179890781native
#> [4] 0.520880179890781native 0.842113716672021native 0.842113716672021native
diff <- convertX(bot_tick_pos, "native", TRUE) - convertX(top_tick_pos, "native", TRUE)

avg_diff <- sqrt(sum(diff^2) / length(diff))
print(avg_diff)
#> [1] 0

Created on 2019-10-18 by the reprex package (v0.2.1)

clauswilke commented 5 years ago

Great, I think we can consider this issue resolved then.

@Ilia-Kosenkov Can you try to recreate your original figure using this new framework, to make sure things work as expected? You'll have to install the current development version of ggplot2, via remotes::install_github("tidyverse/ggplot2").

Ilia-Kosenkov commented 5 years ago

Ok, thank you for clarification. @paleolimbot , I saw some of your recent changes, and it actually improved tick positions. Before updating to the last github commit, the difference was significantly larger. @clauswilke, I can reproduce, but unfortunately, it will take some time. I believe that if reprex gives exactly identical tick positions, it is resolved.

I am unfortunately unfamiliar with the guide(x.sec), so now I wonder if scale_*(sec.axis) and guide(x.sec) use different mechanisms and what is the preferred way to generate secondary axes? Will the support of one of these features be dropped at some point in the future?

paleolimbot commented 5 years ago

My guess would be that both interfaces will be supported for a very long time, so no need to worry. I think that the next release will put the new guide_axis() to the test, and if all goes well, we might be able to move some more responsibility to the guides.

lock[bot] commented 4 years ago

This old issue has been automatically locked. If you believe you have found a related problem, please file a new issue (with reprex) and link to this issue. https://reprex.tidyverse.org/