Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature Request: Allow reversing discrete scales #3115

Closed
billdenney opened this issue Feb 3, 2019 · 7 comments · Fixed by #3426
Closed

Feature Request: Allow reversing discrete scales #3115

billdenney opened this issue Feb 3, 2019 · 7 comments · Fixed by #3426
Labels
feature scales 🐍 tidy-dev-day 🤓

Comments

@billdenney
Copy link

@billdenney billdenney commented Feb 3, 2019

In many scenarios across aesthetics (x, y, fill, colour, and probably anything discrete), it would help to have the option to reverse factor levels on a discrete axis.

What would you think of a PR for one of the options below?

However this is implemented, it would also propagate to a change in the scales package. I think that the simplest change would be to make a scales::reverse_trans_discrete() function, but I've not fully dived in to confirm that.

I think that the simplest and least invasive change would be to add a reverse argument to all the scale_*_discrete() and similar categorical functions (e.g. scale_colour_hue() and scale_colour_viridis_d()). With reverse=TRUE, scales::reverse_trans_discrete() would be used.

library(ggplot2)
d <-
  data.frame(
    x_fac=factor(LETTERS[1:3]),
    y=1:3
  )
ggplot(d, aes(x=x_fac, y=y)) +
  geom_point()

ggplot(d, aes(x=x_fac, y=y)) +
  geom_point() +
  scale_x_reverse()
#> Warning in Ops.factor(x): '-' not meaningful for factors
#> Warning: Transformation introduced infinite values in continuous x-axis
#> Error: Discrete value supplied to continuous scale
# Thanks @jennybc! (from https://gist.github.com/jennybc/6f3fa527b915b920fdd5)
ggplot(d, aes(x=x_fac, y=y)) +
  geom_point() +
  scale_x_discrete(limits=rev(levels(d$x_fac)))

# Implementation option: scale_x_discrete and similar gains a `reverse`
# argument which defaults to `FALSE`
ggplot(d, aes(x=x_fac, y=y)) +
  geom_point() +
  scale_x_discrete(reverse=TRUE)
#> Error in discrete_scale(c("x", "xmin", "xmax", "xend"), "position_d", : unused argument (reverse = TRUE)

Created on 2019-02-03 by the reprex package (v0.2.0).

@clauswilke
Copy link
Member

@clauswilke clauswilke commented Feb 3, 2019

Maybe one option would be to allow functions as limit arguments, just like we do for labels?

library(ggplot2)

ggplot(iris, aes(Species, Sepal.Length)) + geom_boxplot() +
  scale_x_discrete()

# works, reverses labels (but not data)
ggplot(iris, aes(Species, Sepal.Length)) + geom_boxplot() +
  scale_x_discrete(labels = rev)

# doesn't work
ggplot(iris, aes(Species, Sepal.Length)) + geom_boxplot() +
  scale_x_discrete(limits = rev)
#> Error in match(as.character(x), limits): 'match' requires vector arguments

Created on 2019-02-03 by the reprex package (v0.2.1)

@clauswilke
Copy link
Member

@clauswilke clauswilke commented Feb 3, 2019

There's an old (abandoned?) PR about a similar topic for continuous scales: #2334

@billdenney
Copy link
Author

@billdenney billdenney commented Feb 3, 2019

The PR seems like it would be a good fit if applied to discrete scales (and it would help to add that to the documentation with a reversing example, too).

@thomasp85 thomasp85 added the feature label Apr 11, 2019
@hadley
Copy link
Member

@hadley hadley commented Jun 25, 2019

Note that the colour scales have a direction argument which this could replace.

@paleolimbot
Copy link
Member

@paleolimbot paleolimbot commented Jun 25, 2019

Having discrete position scales support functions as the limits argument seems like a reasonable solution to this. All other scales allow this (discrete and continuous). Then you could do limits = rev:

library(ggplot2)

ScaleDiscretePositionFunc <- ggproto(
  "ScaleDiscretePositionReversed", ScaleDiscretePosition,
  get_limits = function(self) {
    if (self$is_empty()) {
      c(0, 1)
    } else if (is.null(self$limits)) {
      self$range$range
    } else if (is.function(self$limits)) {
      self$limits(self$range$range)
    } else {
      integer(0)
    }
  }
)

scale_x_discrete2 <- function(..., expand = waiver(), position = "bottom") {
  sc <- discrete_scale(c("x", "xmin", "xmax", "xend"), "position_d", identity, ...,
                       expand = expand, guide = "none", position = position, super = ScaleDiscretePositionFunc)
  
  sc$range_c <- ggplot2:::continuous_range()
  sc
}

ggplot(mpg, aes(class, cty)) +
  geom_boxplot() +
  scale_x_discrete2(limits = rev)

Created on 2019-06-25 by the reprex package (v0.2.1)

The code that needs to be updated is here:

get_limits = function(self) {
if (self$is_empty()) return(c(0, 1))
self$limits %||% self$range$range %||% integer()
},

@paleolimbot paleolimbot added scales 🐍 tidy-dev-day 🤓 labels Jun 25, 2019
@billdenney
Copy link
Author

@billdenney billdenney commented Jun 25, 2019

@paleolimbot, That seems like a great solution that would allow simple generalization across this and other solutions.

paleolimbot pushed a commit that referenced this issue Jul 16, 2019
* reverse discrete position scales - get_limits
@lock
Copy link

@lock lock bot commented Jan 12, 2020

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/

@lock lock bot locked and limited conversation to collaborators Jan 12, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
feature scales 🐍 tidy-dev-day 🤓
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants