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: enable making copies of trained position scales #3441

Open
clauswilke opened this issue Jul 15, 2019 · 7 comments · May be fixed by #6286
Open

Feature request: enable making copies of trained position scales #3441

clauswilke opened this issue Jul 15, 2019 · 7 comments · May be fixed by #6286
Labels
feature a feature request or enhancement internals 🔎

Comments

@clauswilke
Copy link
Member

To create plots with marginals, it would be helpful if there was a straightforward method to reliably create copies of trained position scales. I'm wondering whether this is possible or can be made possible after @paleolimbot's refactoring of the position scales.

For example, consider this code, which uses the function cowplot::axis_canvas():

library(tidyverse)
library(cowplot)

p1 <- ggplot(iris, aes(Sepal.Length, Sepal.Width, color = Species)) +
  geom_point(alpha = 0.5)

p1

p2 <- axis_canvas(p1, "x", iris, aes(x = Sepal.Length, fill = Species)) +
  geom_density()

p2 + theme_grey()

p1 %>% insert_xaxis_grob(p2) %>% ggdraw()

I'd want to be able to generate the plot p2 by writing something like the following:

p2 <- ggplot(iris, aes(x = Sepal.Length, fill = Species)) +
  geom_density() +
  scale_x_copy(p1)

or maybe

p2 <- ggplot(iris, aes(x = Sepal.Length, fill = Species)) +
  geom_density() +
  scale_x_copy(ggplot_build(p1))

The function scale_x_copy() would take all information (type, limits, expansion, labels) about the x position scale from the built object of p1 and then faithfully reproduce it. Importantly, the scale should not change range or other properties based on the data provided in the second plot.

I'd be happy to implement such a scale myself if somebody can give me some hints about how to best go about it.

@paleolimbot
Copy link
Member

For CoordCartesian(), ggplot_build(p1)$layout$panel_params[[1]]$x is scale-like (i.e., has get_limits(), get_breaks() and get_labels()...it is technically a ViewScale). You might have some trouble with expansion...I forget whether get_limits() returns the expanded range or the original range. I think I thought ahead on this and had it return the unexpanded range and had $dimension() return the expanded range...

@clauswilke
Copy link
Member Author

Yes, this seems correct. In the past, getting ranges of auto-expanded scales, in particular of discrete scales with data extending beyond the standard expanded range, has been difficult. (These arise with ggridges, for example.) Now it seems to work fine. We may want to document the properties of ViewScale a bit more, though, to make this easier to discover for others.

I'll see what I can do with this. It should be possible to convert a ViewScale back into a regular scale by disabling the train() function.

library(ggplot2)
library(ggridges)
#> 
#> Attaching package: 'ggridges'
#> The following object is masked from 'package:ggplot2':
#> 
#>     scale_discrete_manual

p <- ggplot(iris, aes(Sepal.Length, Species)) +
  geom_density_ridges()
p
#> Picking joint bandwidth of 0.181

pb <- ggplot_build(p)
#> Picking joint bandwidth of 0.181

scale_x <- pb$layout$panel_params[[1]]$x
scale_x$get_limits()
#> [1] 3.757376 8.442624
scale_x$dimension()
#> [1] 3.523114 8.676886

scale_y <- pb$layout$panel_params[[1]]$y
scale_y$get_limits()
#> [1] "setosa"     "versicolor" "virginica"
scale_y$dimension()
#> [1] 0.40000 4.25645

Created on 2019-07-16 by the reprex package (v0.3.0)

@paleolimbot
Copy link
Member

Yes, I plan to document ViewScales for #3436 before my internship is done! After #3398 is merged the ViewScales need at least one more round of tightening up.

Another approach may be to add a $clone() method for ranges (the R6 ranges in the dev version of scales already have this), and have Scale$clone() clone the ranges, rather than re-instantiate them. Then you could do ViewScale$scale$clone(). Perhaps ViewScale$clone() should do this (pretend like it's the original scale class).

@clauswilke
Copy link
Member Author

I like the idea of giving ViewScale the functionality to produce a new scale that is a copy of the original one. However, I don't think it should be called ViewScale$clone(), since that name would imply that the ViewScale is being cloned. Maybe ViewScale$clone_scale()? For a Scale$clone() function, my assumption would be that the cloned scale behaves like the original one, i.e., can be further trained. Of course, any clone() function could also take an argument that specifies whether the cloned scale should be permanently frozen or not.

@paleolimbot
Copy link
Member

Just a note that I played around with a $finalize() method for scales but couldn't replicate some expansion corner case behaviour, which is how ViewScales came to be. I'm not sure I've convinced myself that a ViewScale is better than a Scale$finalize() clone like you've described, but the two are interface-compatible so from an internal interface perspective there isn't really a difference.

@paleolimbot paleolimbot added feature a feature request or enhancement internals 🔎 labels Jul 22, 2019
@teunbrand
Copy link
Collaborator

@clauswilke Is this still something you think you'd use / would like to use?

@clauswilke
Copy link
Member Author

Not actively working on this but still think it would be good to have. Big picture I think it would be great if we had a way of making marginal plots that isn't a huge hack. Something like: make center plot; make marginal plots copying scales; stitch everything together with patchwork.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature a feature request or enhancement internals 🔎
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants