Make sure ggblend works with ggrastr
mjskay opened this issue · comments
Will probably have to investigate ggrastr internals for this. These two plots should be different but aren't:
library(ggplot2)
library(ggblend)
library(ggrastr)
diamonds |>
ggplot() +
geom_point(aes(carat, price, colour = cut)) |> rasterize(dpi = 30)
diamonds |>
ggplot() +
geom_point(aes(carat, price, colour = cut)) |> blend("multiply") |> rasterize(dpi = 30)
Second one should look like this:
diamonds |>
ggplot() +
geom_point(aes(carat, price, colour = cut)) |> blend("multiply")
Looking at ggrastr, it looks like it rasterizes at the geom level, not the layer level, by overriding Geom$draw_panel()
:
However, ggblend applies its operations at the layer level via Layer$draw_geom()
:
Lines 48 to 61 in 54bd316
My guess is that ggrastr should do the same?
Hi Matthew, I contributed the rasterise()
code to {ggrastr} so I have a passing familiarity with it, but not so much with {ggblend}. IIRC the rasterisation takes place at draw-time, so that the number of pixels scale appropriately to the device/window size. Is ggblend pre-rendering the layers during the ggplot2::ggplot_gtable()
step?
ggblend works by replacing each layer being blended with a "HiddenLayer"
, which when Layer$draw_geom()
is called renders itself and stores the rendered grobs, but displays nothing. It adds a "TransformedLayer"
to the layer list, which when Layer$draw_geom()
is called takes the HiddenLayers' grobs and does the appropriate transformation then renders them.
So say you have something like
list(
<LayerInstance>,
<LayerInstance>
) |> blend("multiply")
This will return something like:
list(
<HiddenLayer>,
<HiddenLayer>,
<TransformedLayer>
)
which can be added to a ggplot object. I think this would be compatible with {ggrastr} if it, instead of wrapping Geom$draw_panel()
to rasterize a layer, it wrapped Layer$draw_geom()
to rasterize the layer (which seemed the natural wrapping point to me since it is highest up in the call chain). However, if ggblend should be wrapping at a different point (like Geom$draw_panel()
), I could try to change it.
Looking at a few different packages with this type of functionality, I see:
- {relayer} wraps
Geom$draw_panel()
here - {ggfx} wraps
Geom$draw_layer()
here - {ggrastr} wraps
Geom$draw_panel()
here
I feel like there's not a big difference between these. Basically, each package has to intervene somewhere in the stack of Layer$draw_geom()
-> Geom$draw_layer()
-> Geom$draw_panel()
. My logic was that wrapping further down the stack has greater potential for creating incompatibilities between packages, in case a geom author decides to override any of the preceding functions to do something different (including, say, not calling down to the lower functions at all). Thus, I overrode at Layer$draw_geom()
. I don't see a big difference between that and overriding at Geom$draw_layer()
(as Layer$draw_geom()
basically just does a check and then calls Geom$draw_layer()
), so I could probably switch to that. However, I'm not sure overriding at Geom$draw_panel()
would be the best approach if it can be avoided, just in case a geom implements a weird version of Geom$draw_layer()
...
But, I'm happy to change how ggblend is doing this if I've misinterpreted the ggplot internals in some way here.
I don't think you're misinterpreting the internals, this sounds about right. In any case, yeah if we move the ggrastr adjustments up a level, it should work fine:
library(ggplot2)
library(ggblend)
library(ggrastr)
# Manually override the layer method
rasterise.Layer <- function(input, dpi=NULL, dev="cairo", scale=1, ...) {
dev <- match.arg(dev, c("cairo", "ragg", "ragg_png"))
if (is.null(dpi)) {
dpi <- getOption("ggrastr.default.dpi")
}
# Take geom from input layer
old.geom <- input$geom
# Reconstruct input layer
ggproto(
NULL, input,
geom = ggproto(
NULL, old.geom,
draw_layer = function(...) {
grobs <- old.geom$draw_layer(...)
lapply(grobs, function(grob) {
grob$dpi <- dpi
grob$dev <- dev
grob$scale <- scale
return(grob)
})
}
)
)
}
file <- tempfile(fileext = ".png")
png(file, type = "cairo")
diamonds |>
ggplot() +
geom_point(aes(carat, price, colour = cut)) |> blend("multiply") |> rasterize(dpi = 30)
dev.off()
#> png
#> 2
knitr::include_graphics(file)
Created on 2023-05-22 with reprex v2.0.2
Closing this as it should be resolved by VPetukhov/ggrastr#34 thanks to the speedy work of @teunbrand :)