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 function as argument value in scale_*(limits=) #2307

econandrew opened this issue Oct 22, 2017 · 3 comments


Copy link

econandrew commented Oct 22, 2017

Request is to allow limits arg in e.g. scale_x_continuous to accept a function which takes the default limits and returns new limits, in parallel with the same functionality in breaks and labels.

At present it seems like the two options are (1) specify literal limits, which is inflexible (2) calculate the preferred limits outside of the ggplot statement, but that is messy and can result in redoing calculations already happening inside ggplot (e.g. for position="stack").

It looks like this may be a one line change in scale-.r's get_limits, but I don't know my way around well enough to be sure.

Motivating example follows.


p <- ggplot(mtcars) + geom_point(aes(cyl,mpg))

p # But suppose we don't like the default y-axis breaks...

# ...we have a general mechanism in breaks= to modify them with a function...
breaker <- function(by) {
  function(limits) {
    low <- floor(limits[1]/by)*by
    high <- ceiling(limits[2]/by)*by
    seq(low, high, by)

# ...but it doesn't override the limits so we get an (IMHO) ugly y axis where
# the data exceeds the range of the tick marks...
p + scale_y_continuous(breaks = breaker(10))

# In principle we can function-ise the limits in the same way
limiter <- function(by) {
  function(limits) {
    low <- floor(limits[1]/by)*by
    high <- ceiling(limits[2]/by)*by
    c(low, high)

# But for now the best we can do is manually override the limits
p + scale_y_continuous(breaks = breaker(10), limits = limiter(10)(range(mtcars$mpg)))

# Feature request: if limits arg is a function, pass it the existing limits automatically
p + scale_y_continuous(breaks = breaker(10), limits = limiter(10))
#> Warning in$limits): applied to non-(list or vector) of
#> type 'closure'
#> Error in rep(yes, length.out = length(ans)): attempt to replicate an object of type 'closure'
Copy link

has2k1 commented Oct 22, 2017

Is using the expand parameter not sufficient?

Copy link
Contributor Author

I don't think so. Following on the example above, you might want a y-scale that ends exactly at multiples of 10, e.g.

p + scale_y_continuous(breaks = breaker(10), limits = limiter(10)(range(mtcars$mpg)), expand = c(0, 0))

I don't see how you can achieve that in a general way using expand alone, since it can only add/multiply by a fixed amount. You could add on whatever increment was necessary to (in the example case) bring the scale up to exactly 40, but again that would require replicating the limits calculation outside of the ggplot2 to figure out what the increment would be - or just hard-coding it, e.g.

p + scale_y_continuous(breaks = breaker(10), expand = expand_scale(add = c(0, 6.1)))

The other advantage of setting the limits rather than expand is that it will feed into the default calculation of the breaks, so in my example above you actually don't need to specify the breaks, changing the limits changes the breaks too:

p + scale_y_continuous(limits = limiter(10)(range(mtcars$mpg)))

Copy link

hadley commented Oct 30, 2017

I like the idea in principle, but it feels it bit too different to breaks/labels since if you supply an argument to limits, the default calculation still has to take place. That said, I'd still review a PR.

@hadley hadley closed this as completed Oct 30, 2017
@lock lock bot locked as resolved and limited conversation to collaborators Jun 18, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
None yet
None yet

No branches or pull requests

3 participants