-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Feature request: allow access to the trained scales from the layout
object
#3116
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
Comments
I think this feature would also help with generating filled contours at draw time from the new binning scale @thomasp85 is implementing. See #3044 and #3096. |
There are definitely enough meaningful examples to make us consider how to support it. I don’t personally think we should make the ScalesList object available as it is not currently an exported class we have committed to keeping stable. Making it available directly would limit our possibilities in the future all for the sake of a few (albeit important) needs... |
An approach I would like would be some sort of method in the geom for acquiring specific scales - eg |
How about as a method of the layout (i.e. |
I think whatever solution we come up with requires careful thought so we don't box ourselves in for future development. We've talked a lot about layer-specific scales (e.g., different color scales for different layers), and if at all possible the approach taken now should be future-proof for that. (Even if we don't really know yet how to set up layer-specific scales.) This reasoning would suggest that it would be best if the scales could be made available through the Layer object, or directly through the Geom. I'm not immediately sure how to do this, but I think pondering a little longer is the right strategy. |
That's a great point...thanks to you both for considering this and let me know if there is anything I can do! |
If you want to dig around some more in the ggplot2 code to develop potential strategies of how we can get scales info into the geom that would be helpful. Ideally, before we start coding, we'd have a list of potential approaches, each with pros and cons, and then we can make an informed decision about how to proceed. |
👍 will do! |
I tried a few things! I'm sure there are other ways to go about this...
library(ggplot2)
GeomScaleInfo <- ggproto(
"GeomScaleInfo", Geom,
required_aes = "x",
draw_panel = function(self, data, panel_params, coord) {
x_scale <- self$layer$get_scale("x", 1L)
col_scale <- self$layer$get_scale("colour")
x_range <- x_scale$range$range
col_limits <- col_scale$range$range
if(is.null(x_range)) {
text <- "NULL x-range"
} else {
text <- sprintf(
"X-scale: %s-%s\ncol-scale: %s items",
x_range[1], x_range[2], length(col_limits)
)
}
grid::textGrob(text, x = 1, y = 1, hjust = 1.1, vjust = 1.5)
}
)
geom_scale_info <- function() {
layer(
geom = GeomScaleInfo, stat = "identity", data = data.frame(x = 1), mapping = aes(x = x),
position = "identity",
params = list(), inherit.aes = FALSE, show.legend = NA
)
}
ggplot(mpg, aes(displ, hwy, col = class)) + geom_point() + geom_scale_info() Created on 2019-02-13 by the reprex package (v0.2.1)
library(ggplot2)
GeomScaleInfo <- ggproto(
"GeomScaleInfo", Geom,
required_aes = "x",
# this approach requires overriding draw_layer() to pass on the layout object
draw_layer = function(self, data, params, layout, coord) {
if (ggplot2:::empty(data)) {
n <- if (is.factor(data$PANEL)) nlevels(data$PANEL) else 1L
return(rep(list(zeroGrob()), n))
}
# Trim off extra parameters
params <- params[intersect(names(params), self$parameters())]
args <- c(list(quote(data), quote(panel_params), quote(coord), quote(layout)), params)
lapply(split(data, data$PANEL), function(data) {
if (empty(data)) return(zeroGrob())
panel_params <- layout$panel_params[[data$PANEL[1]]]
do.call(self$draw_panel, args)
})
},
draw_panel = function(self, data, panel_params, coord, layout) {
x_scale <- layout$get_scale("x", 1L)
col_scale <- layout$get_scale("colour")
x_range <- x_scale$range$range
col_limits <- col_scale$range$range
if(is.null(x_range)) {
text <- "NULL x-range"
} else {
text <- sprintf(
"X-scale: %s-%s\ncol-scale: %s items",
x_range[1], x_range[2], length(col_limits)
)
}
grid::textGrob(text, x = 1, y = 1, hjust = 1.1, vjust = 1.5)
}
)
geom_scale_info <- function() {
layer(
geom = GeomScaleInfo, stat = "identity", data = data.frame(x = 1), mapping = aes(x = x),
position = "identity",
params = list(), inherit.aes = FALSE, show.legend = NA
)
}
ggplot(mpg, aes(displ, hwy, col = class)) + geom_point() + geom_scale_info() Created on 2019-02-13 by the reprex package (v0.2.1) |
The first option seems nicer and more general to me. Not sure if there are any major downsides we're overlooking, though. Usually, a main design principle of software engineering is to avoid circular dependencies, and it seems this introduces one, since layer knows of geom and geom will know of layer after the change. |
A general rule for most ggproto objects is that they should be stateless factories, so I would prefer a setup that keeps this |
Layer info could be included in the data (similar to how the data contains a |
There's an additional complication: if we change the arguments of Is it possible to attach this information to either |
I don't think the arguments would need to be changed to implement There's probably some other ways to get the layer ID into the draw method...the |
“Just” overwriting |
There's one other strategy for handing down information that we haven't considered yet: We can add new member functions that provide a geom or layer with whatever information it needs later, so that it can store it. This is less elegant than expanding the number of arguments in |
Wouldn’t that still make the geoms stateful? |
Yes, that's how I implemented the stateful version of the Plot/Layer/Geom stack (a |
@thomasp85 Yes, you're right, that won't work. I'm coming around to @paleolimbot's perspective that Lines 16 to 31 in 26cd107
It could hold information about other scales as well. I also think that requiring geoms to overwrite
Most geoms wouldn't need this info and thus wouldn't have to change in any way. Any geom that needs this info would have to reimplement The alternative would be to add a new argument to |
It could be that |
Ah, maybe that's a workable idea. I'm still worried about the |
Is there an easy way to find out how many extension packages have redefined |
Nothing that causes any tests to fail...I'll run a revdepcheck as well. |
I forgot that revdepcheck for ggplot2 incapacitates my computer for a day...based on the preliminary results it seems unlikely it's common to override |
How about passing the master...paleolimbot:smarter-layout library(ggplot2)
GeomScaleInfo <- ggproto(
"GeomScaleInfo", Geom,
required_aes = "x",
# this approach requires overriding draw_layer() to pass on the layout and layer objects
draw_layer = function(self, data, params, layout, coord, layer) {
if (ggplot2:::empty(data)) {
n <- if (is.factor(data$PANEL)) nlevels(data$PANEL) else 1L
return(rep(list(zeroGrob()), n))
}
# Trim off extra parameters
params <- params[intersect(names(params), self$parameters())]
args <- c(list(quote(data), quote(panel_params), quote(coord), quote(layout), quote(layer)), params)
lapply(split(data, data$PANEL), function(data) {
if (empty(data)) return(zeroGrob())
panel_params <- layout$panel_params[[data$PANEL[1]]]
do.call(self$draw_panel, args)
})
},
draw_panel = function(self, data, panel_params, coord, layout, layer) {
x_scale <- layout$get_scale("x", data$PANEL[1], layer)
col_scale <- layout$get_scale("colour", data$PANEL[1], layer)
x_range <- x_scale$range$range
col_limits <- col_scale$range$range
text <- sprintf(
"X-scale: %s-%s\ncol-scale: %s items",
x_range[1], x_range[2], length(col_limits)
)
grid::textGrob(text, x = 1, y = 1, hjust = 1.1, vjust = 1.5)
}
)
geom_scale_info <- function() {
layer(
geom = GeomScaleInfo, stat = "identity", data = data.frame(x = 1), mapping = aes(x = x),
position = "identity",
params = list(), inherit.aes = FALSE, show.legend = NA
)
}
ggplot(mpg, aes(displ, hwy, col = class)) + geom_point() + geom_scale_info() Created on 2019-02-19 by the reprex package (v0.2.1) |
I've lost track of what you want with layer? wasn't the intend to access scales within the |
I don't need the layer at all, unless the scale depends on it. @clauswilke seemed to indicate that layer-specific scales might occur in the future. All I need to implement lazy rasters is |
My concern was that |
There are good reasons for this in the raster context as well...I circumvent aesthetics/scales for RGB(A) rasters, but I am essentially mapping band1 to red, band2 to green, band3 to blue, and band4 to alpha, and those scales are not relevant to other layers. I can also think of some uses for scales that depend on the panel. The position scales are currently implemented this way, and I could see how it may make sense for another aesthetic to have free scales as well with multiple panels, even if that's a long way off. So, I think I could also see how working it into |
Can we first implement the per-layer scale that belong to |
It sounds like you've considered all the issues I've bought up, so I'm happy to return the reins back to y'all. (But feel free to ping me if you want more advice, regardless of my limit memory of this code) |
@clauswilke : Passing just a In particular, it may better define the role of the Alternatively, define the |
I don't think we should export Adding more things to Lines 204 to 206 in 033fb52
I'm not sure of the performance implications if every layer stores the raw data, though. Are these all just references that don't cost anything? |
Actually, let me qualify my previous comment: I'm somewhat concerned about adding the aesthetic mapping to the |
Got it! Something like this? master...paleolimbot:layer-layer-params I think I stepped in a deep issue for my second ever in ggplot2...I have a feeling this is better suited for somebody who is more familiar with the future needs of ggplot2. I will happily use whatever it's decided is best for this (or I won't use anything if it's decided it's too much of a hack). |
Does it work? I don't remember if the scales are available at that point in the plot build. In any case, I think you've mostly solved the issue. It's just details at this point. @thomasp85 Do you have an opinion on whether the |
Hmm, agreed. Even the per-layer scales were to be implemented as the one that belongs to |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
I created a separate issue for the |
@paleolimbot Does the latest version of your code solve the problem you set out to solve? If yes, then I think you should open a PR and then we can resolve any remaining issues. Note that the PR will require a news item, as described here: https://style.tidyverse.org/news.html#bullets |
Sorry, I lost track of the thread and had to Ph.D. for a few days. Yes, the latest version of my code solves the problem, although I haven't unit tested yet. I will add some tests and the news item and PR after I defend my comps (today)! |
Good luck for your comps! |
The example in #3116 (comment) works because |
Closing this as it seems stale. We can reopen if anyone feels strongly about it |
This is a fairly easy (I think) feature to add that would help me implement lazy reading of huge raster files (a stars proxy object), such that they can be downsampled appropriately. To do this, I would need access to the trained scales (for the
fill
andalpha
aesthetics at draw time. Right now there is no way to access the trained scales except limited information about them in thepanel_params
andcoordinates
objects. I think that a reference to theScalesList
object could be included as an element of thelayout
object without any consequences? TheGeom
has access to thelayout
at draw time.It is a completely valid point that this would be pushing ggplot2 where it perhaps should not go, coercing it to do things that it was not meant to do. I would argue that it is necessary if spatial rasters are ever going to be plotted in ggplot by those of us who don't have an intimate knowledge of downsampling (and maybe they shouldn't be). There is a brief discussion on the implementation of this at paleolimbot/ggspatial#17 . A reprex is included below.
Created on 2019-02-03 by the reprex package (v0.2.1)
The text was updated successfully, but these errors were encountered: