ggplot2
ggplot2 copied to clipboard
scale_*_reverse() is ignored when limits are set on Coord
Since I updated R to version 4.0 the scale_x_reverse () function has stopped working together with renderPlot () or renderPlotly( ), which I use in a shiny app that I develop ... However when I test scale_x_reverse () out of the render it works fine. It is worth mentioning that in all other versions of R it was working normally in both Windowns (7, 8 and 10) and Linux (Ubunto, mint, Debian, Suse). Now I am use Windowns 10, R (4.0), RStudio (1.2.5042).
Can someone help me?
Brief description of the problem
# ` # Variables
peran_multi <<- 0
ysup_multi <- max(NMRData[1,])
yinf_multi <- min(NMRData[1,])
ysup_multi <- ysup_multi + ysup_multi*0.03
chkzoom_multi <<- 1
idb_multi <<- 0
# Data.frame
testy_multi <<- data.frame(Chemical_Shift=CS_values_real[1,],Spectrum=NMRData[1,])
ranges_multi <- reactiveValues(x = c((min(testy_multi$Chemical_Shift)), max(testy_multi$Chemical_Shift)), y = c(-100,ysup_multi))
# ranges_multi_plot <- reactiveValues(x = sort(c((min(testy_multi$Chemical_Shift)), max(testy_multi$Chemical_Shift)), decreasing = TRUE), y = c(yinf_multi,ysup_multi))
spectrums_multi <- reactiveValues(dat = data.frame(Chemical_Shift=CS_values_real[1,],Spectrum=NMRData[1,]))
# Plot
output$plot_multi <- plotly::renderPlotly({
ggplot2::ggplot(spectrums_multi$dat,ggplot2::aes(Chemical_Shift,Spectrum)) + ggplot2::geom_line(color='blue') + ggplot2::scale_x_reverse() +
ggplot2::coord_cartesian(xlim = ranges_multi$x, ylim = ranges_multi$y, expand = FALSE) +
ggplot2::theme(axis.text.x = ggplot2::element_text(size = 12, color = "#000000"),
axis.text.y = ggplot2::element_text(size = 12, color = "#000000"),
title = ggplot2::element_text(face = "bold", color = "#000000", size = 17),
axis.title = ggplot2::element_text(face = "bold", color = "#000000", size = 15)
) +
ggplot2::labs(x = "Chemical Shift", y = "Intensity")
})
`
Could you ask on plotly's repo (with a minimal reproducible example)? I'm not sure if this is what should be fixed on plotly's side, but we have too little knowledge about plotly's internals to identify the cause of the problem.
Sorry yutannihilation for not put a reproducible example. I am a new programmer and this is my first question on forum. try to see this new reproducible example now
`library(shiny)
library(ggplot2)
library(plotly)
ui <- fluidPage(
fluidRow(align = "center",
mainPanel(
plotly::plotlyOutput("plot_multi")
)
)
)
server <- function(input, output) {
# Data.frame
z <<- runif(1001, min=0, max=1000000)
f <<- seq(0,10,0.01)
testy_multi <<- data.frame(Chemical_Shift=f,Spectrum=z)
ranges_multi <- reactiveValues(x = c((min(testy_multi$Chemical_Shift)), max(testy_multi$Chemical_Shift)), y = c(-100,1000000))
spectrums_multi <- reactiveValues(dat = data.frame(Chemical_Shift=f,Spectrum=z))
# Plot
output$plot_multi <- plotly::renderPlotly({
ggplot2::ggplot(spectrums_multi$dat,ggplot2::aes(Chemical_Shift,Spectrum)) + ggplot2::geom_line(color='blue') +
ggplot2::coord_cartesian(xlim = ranges_multi$x, ylim = ranges_multi$y, expand = FALSE) + ggplot2::scale_x_reverse() +
ggplot2::theme(axis.text.x = ggplot2::element_text(size = 12, color = "#000000"),
axis.text.y = ggplot2::element_text(size = 12, color = "#000000"),
title = ggplot2::element_text(face = "bold", color = "#000000", size = 17),
axis.title = ggplot2::element_text(face = "bold", color = "#000000", size = 15)
) +
ggplot2::labs(x = "Chemical Shift", y = "Intensity")
})
}
shinyApp(ui = ui, server = server)
`
Thanks for the reproducible example, I confirmed your example works with ggplot2 v3.2.1 but won't with v3.3.0 (or the current master). But, again, could you file this issue on plotly's repo? I wasn't complaining about the lack of reproducibility, I'm just saying we cannot fix this (at least without any help from plotly's maintainers).
Thanks for responding yutannihilation , I agree with what you said about going to plotly and now I go to the forum, but I have the same problem using renderPlot () which is not from plotly. This was the reason for looking for this forum before.
@vitor-mendes-iq Ah, OK. I misunderstood your point, sorry... Here's a minimal reprex:
library(ggplot2)
d <- data.frame(x = 1:2)
p <- ggplot(d, aes(x, x)) +
geom_point() +
scale_x_reverse()
p
p + coord_cartesian(xlim = range(d$x))
Created on 2020-05-23 by the reprex package (v0.3.0)
The reversed x scale is ignored because you specifies xlim
in the order.
@thomasp85 Sorry, at the time of implementing #3958, I thought this is the correct behaviour, but I found it didn't behave so in the past. Do you think I need to fix this before releasing v3.3.1?
I would expect all these plots to produce the same result. That is not currently the case. I'm particularly confused by the cases that remove the points. Note that this is not a rounding error; if I make the limits broader the points are still removed.
library(ggplot2)
d <- data.frame(x = 1:2)
p <- ggplot(d, aes(x, x)) + geom_point()
p + xlim(2, 1)
p + scale_x_continuous(limits = c(2, 1))
#> Warning: Removed 2 rows containing missing values (geom_point).
p + coord_cartesian(xlim = c(2, 1))
p + scale_x_reverse()
p + scale_x_reverse(limits = c(1, 2))
#> Warning: Removed 2 rows containing missing values (geom_point).
p + scale_x_reverse() + coord_cartesian(xlim = c(1, 2))
Created on 2020-05-22 by the reprex package (v0.3.0)
The difference between xlim()
and scale_x_continuous()
is that the former automatically sets the trans to reverse when the limits are specified in the reverse order.
https://github.com/tidyverse/ggplot2/blob/48268385c414702bc343e46d51144846d39232fa/R/limits.r#L118-L122
This heuristic made me think that the order of the limit do matter, and maybe the user is supposed to specify them in the same order of the scale...? Sorry, I'm still confused.
I guess this is also a consistent principle: If explicit limits are provided, they always supersede the natural direction of the scale.
Ah, thanks, makes sense. Then, we have two issues to consider:
- When scale limits are provided in the opposite order to the direction of the scale, the limits are ignored and all data are removed (This should be fixed).
- When coord limits are provided in the opposite order to the direction of the scale, the limits supersede the natural direction of the scale. This seems to do the right thing, but it didn't behave so before v3.2.1 (v3.3.0 was broken here).
The examples below shows issue 2. What I was wondering is whether we should fall back to the behaviour of v3.2.1, but now I feel the current behaviour is OK.
d <- data.frame(x = 1:2)
ggplot(d, aes(x, x)) +
geom_point() +
scale_x_reverse() +
coord_cartesian(xlim = range(d$x))
v3.2.1
v3.3.0
current master
@yutannihilation You are right, but version 3.3 does not have scale_x_reverse () responding within renderPlot () and renderplotly (), while version 3.2.1 does. Out of renders 3.3 works perfectly
There's another issue to consider: scale_*_reverse()
uses reverse_trans()
. But what if we use a scale with a different transformation (e.g., scale_*_log10()
) and then specify reversed limits? The user would expect this to work also.
@vitor-mendes-iq
As you can see above, v3.3.0 has a problem about scale_*_reverse()
no matter it's used in Shiny or not. Btw, you can change this line
ggplot2::coord_cartesian(xlim = ranges_multi$x, ylim = ranges_multi$y, expand = FALSE) + ggplot2::scale_x_reverse() +
to this (I think we don't reach a consensus whether this is the right way or not, but if you need an immediate workaround, this should work, at least until we make some change):
ggplot2::coord_cartesian(xlim = rev(ranges_multi$x), ylim = ranges_multi$y, expand = FALSE) +
@clauswilke Yeah, the user does expect so... Do you mean it should work? Currently we can choose only one trans, but it might not be very clear to the users (e.g. https://github.com/tidyverse/ggplot2/issues/4014).
Yes, the more I think about it, the more I believe reversing a scale should be handled separately from transformations.
I got it. Sounds good to me!
I ran into this last week. Not sure I completely understand the path forward but here an example from my use case.
this works as expected
library(ggplot2)
ggplot(mtcars, aes(x=disp, y=mpg))+geom_point()+coord_cartesian(xlim=c(0,200))
but if you add a scale_x_reverse() it doesn't do anything (and oddly removes the x axis text!?)
ggplot(mtcars, aes(x=disp, y=mpg))+geom_point()+scale_x_reverse()+coord_cartesian(xlim=c(0,200))
instead I had to do
ggplot(mtcars, aes(x=disp, y=mpg))+geom_point()+scale_x_reverse()+coord_cartesian(xlim=c(200,0))
from a user pov I don't expect to have to change my coord_cartesion limits order just because I reversed the scale direction. that would be unnecessarily verbose. I think this happened during the CoordCartesian refactor because it doesn't happen with 3.2.1. I was a bit surprised because the release notes explicitly state there were no user facing changes :)
I think the answer is yes, but is the proposed solution to revert to the old behavior?
Yes, the more I think about it, the more I believe reversing a scale should be handled separately from transformations.
>sessionInfo()
R version 3.6.3 (2020-02-29)
Platform: x86_64-w64-mingw32/x64 (64-bit)
Running under: Windows 10 x64 (build 18363)
Matrix products: default
locale:
[1] LC_COLLATE=English_United States.1252 LC_CTYPE=English_United States.1252
[3] LC_MONETARY=English_United States.1252 LC_NUMERIC=C
[5] LC_TIME=English_United States.1252
attached base packages:
[1] stats graphics grDevices utils datasets methods base
other attached packages:
[1] ggplot2_3.3.1 devtools_2.3.0 usethis_1.6.1
loaded via a namespace (and not attached):
[1] Rcpp_1.0.4.6 pillar_1.4.4 compiler_3.6.3 prettyunits_1.1.1 remotes_2.1.1
[6] tools_3.6.3 testthat_2.3.2 digest_0.6.25 pkgbuild_1.0.8 packrat_0.5.0
[11] pkgload_1.0.2 tibble_3.0.1 lifecycle_0.2.0 memoise_1.1.0 evaluate_0.14
[16] gtable_0.3.0 pkgconfig_2.0.3 rlang_0.4.6 cli_2.0.2 rstudioapi_0.11
[21] yaml_2.2.1 xfun_0.14 dplyr_0.8.5 withr_2.2.0 knitr_1.28
[26] vctrs_0.3.0 desc_1.2.0 fs_1.4.1 rsthemes_0.0.7 tidyselect_1.1.0
[31] rprojroot_1.3-2 grid_3.6.3 glue_1.4.1 R6_2.4.1 processx_3.4.2
[36] fansi_0.4.1 rmarkdown_2.2 sessioninfo_1.1.1 farver_2.0.3 purrr_0.3.4
[41] callr_3.4.3 magrittr_1.5 backports_1.1.7 scales_1.1.1 ps_1.3.3
[46] ellipsis_0.3.1 htmltools_0.4.0 assertthat_0.2.1 colorspace_1.4-1 labeling_0.3
[51] munsell_0.5.0 crayon_1.3.4
from a user pov I don't expect to have to change my coord_cartesion limits order
You might not expect, but another user might. I'm yet to figure out what is the correct semantics here... Anyway thanks for your feedback.
(Mainly a note to self) coord_sf()
seems to ignore reversed limits. Should it respect?
library(ggplot2)
library(patchwork)
nc <- sf::st_read(system.file("shape/nc.shp", package = "sf"), quiet = TRUE)
p <- ggplot(nc) +
geom_sf(aes(fill = AREA))
p1 <- p + coord_sf(xlim = c(-76, -82))
p2 <- p + coord_sf(xlim = c(-82, -76))
p1 / p2
Created on 2020-06-28 by the reprex package (v0.3.0)
I think we can safely ignore this unless it is dead simple. The application of reversed spatial coords is doubtful
Agreed, I think it's fine coord_sf()
ignores reversed limits. Only concern is that it might look inconsistent with the current behaviour of coord_cartesian()
.
I don’t see a big issue in simply documenting this lack of functionality for coord_sf()
I agree it's not a big issue. Thinking this again, maybe we should start from assuming the reversing functionality is a special feature of CoodCartesian
? Apparently it's up to Coord how to treat the limits.
Yeah, I think that sounds reasonable
Thanks, I think I got less confused now...
Looks like you've got this all sorted. I just wanted to add that I wouldn't even know how to implement coordinate reversion in coord_sf()
in the general case, because projections can do pretty arbitrary things to the coordinate system, so it's not even clear what numbers to revert.
One issue that may be useful for coord_sf()
(but surprisingly difficult to implement) is that for long-lat coordinate systems the order of the x limits could specify which way round the globe you go. I.e., -178:178 would be a map of the entire world excluding a little sliver around the international date line, but 178:-178 would be a map of just the region around the international date line.
That would indeed be super nice, but I think we should wait until someone super motivated comes along. It seems like the cost/benefit of this is pretty low
Have we aligned around the idea of separating scale reversion out of the transformation completely? I think this is the correct approach as the current system is way too limited and has resulted in some nonsensical code paths to work around the limitations?
I think so, yes.
I'm not sure whether the thread has drifted away, or I might be missing something, but the following still shows an unintended behavior:
library(dplyr, warn.conflicts = FALSE)
library(ggplot2)
library(tibble)
mtcars %>%
rownames_to_column("cars") %>%
ggplot(aes(x = mpg, y = cars)) +
geom_col() +
scale_x_reverse() + # <------------------I added this, then why does the plot remain left-to-right?
coord_cartesian(xlim = c(10, 30)) # <------- had I taken away this line, the plot would have been reversed to right-to-left
Created on 2021-08-16 by the reprex package (v2.0.0)
Is there any conclusion regarding this?
I think it might be possible to implement reverse scales implicitly by exposing the rescaler
argument on continuous position scales.
This argument is available in continuous_scale()
, so the internal changes needed would be pretty minimal. One benefit might be that it circumvents the ambiguity of setting the limits in the reverse order in the coord/scale.
An example with a date scale, that currently doesn't have a reverse variant (as mentioned in #4014).
library(ggplot2)
library(scales)
# Hacky workaround for demo purposes
scale_x_date2 <- function(..., rescaler) {
x <- scale_x_date(...)
x$rescaler <- rescaler
x
}
# The default `to` argument of the `rescale()` function is `c(0, 1)`.
# Here, we reverse that.
invert_scale <- function(x, to = c(1, 0), from = range(x)) {
rescale(x, to, from)
}
ggplot(economics, aes(date, unemploy)) +
geom_line() +
scale_x_date2(rescaler = invert_scale)
To show that it plays nice with coord limits:
ggplot(economics, aes(date, unemploy)) +
geom_line() +
scale_x_date2(rescaler = invert_scale) +
coord_cartesian(xlim = as.Date(c("1990-01-01", "1980-01-01")))
Simultaneous with another transformation:
scale_y_continuous2 <- function(..., rescaler) {
x <- scale_y_continuous(...)
x$rescaler <- rescaler
x
}
ggplot(pressure, aes(temperature, pressure)) +
geom_point() +
scale_y_continuous2(trans = "log10", rescaler = invert_scale)
Created on 2021-09-02 by the reprex package (v1.0.0)
One limitation as far as I know is that coord_sf()$transform
and coord_trans()$transform
bypass the scales' rescaler
function. So these coords wouldn't work with an exposed rescaler
argument, but they currently don't work well with scale_{x/y}_reverse()
either (#4206 for example, or try coord_trans(y = "log10")
with scale_y_reverse()
).