Open jimjam-slam opened 3 years ago
It's quite possibly that simple. Worth it to try out.
Unfortunately not!
library(tidyverse)
library(ggtext)
{
ggplot(mtcars) +
aes(mpg, hp) +
geom_point() +
theme(
plot.title = element_textbox(
colour = "white",
fill = grid::linearGradient(
colours = c("#020024", "#102b81", "#833ab4"),
stops = c(0, 0.4, 1)))) +
labs(title = "Hello gradients!")
} %>% ggsave('test.pdf', .)
Saving 7 x 7 in image
# Error in bl_render(x$vbox_outer, x_pt, y_pt) :
# Not compatible with STRSXP: [type=list].
I'm not so familiar with how STRSXP works, but I'll keep tinkering!
Ok. It means that somewhere in the code base we assume that fill
is a string. May be difficult to track down and change.
I should note that this example, modelled on the one in the technical report for the gradients feature, does work:
library(tidyverse)
library(ggtext)
library(grid)
cairo_pdf('test.pdf')
p1 <- ggplot(mtcars) +
aes(mpg, hp) +
geom_point() +
theme_grey(base_size = 16) +
theme(
plot.title = element_textbox(
colour = "white",
fill = 'blue')) +
labs(title = "Hello gradients!")
p1
grid.force()
# use grid.ls() to identify the title background
# here, it's "gridtext.ect.7"
grid.edit("gridtext.rect.7", grep = TRUE,
gp = gpar(fill = linearGradient(
colours = c("#020024", "#102b81", "#833ab4"),
stops = c(0, 0.4, 1))))
dev.off()
So I can see one place where this might be explicitly assumed, in grid-renderer.h
at line 21:
RObject gpar_lookup(List gp, const char* element) {
if (!gp.containsElementNamed(element)) {
return R_NilValue;
} else {
return gp[element];
}
}
(It looks like all Nope, my bad; those are the names of gpar
elements are assumed to be strings, which squares with the existing docs.)gp
elements.
This is called for fill
further down, when rect()
is deciding whether to draw anything (grid-renderer.h
at line 74):
RObject fill_obj = gpar_lookup(gp, "fill");
if (!fill_obj.isNULL()) {
CharacterVector fill(fill_obj);
if (fill.size() > 0 && !CharacterVector::is_na(fill[0])) {
have_fill_col = true;
}
}
As far as I can tell, fill_obj
and fill
don't go any further; this appears just to be about flagging have_fill_col
in order to decide whether to draw a grob (although gp
does get passed wholesale to either rect_grob()
or roundrect_grob()
).
I'm not entirely sure how to modify these sections to accommodate fill
potentially being a list, though. Can we declare element
as an RObject
instead? Will that still work if it is indeed a string? Will gp.containsElementNamed()
and fill.size()
work?
Maybe CharacterVector fill(fill_obj);
is the critical line here?
I tried running with this. No errors, but specifying a gradient silently produced a corrupt PDF. (I broke regular fills but got 'em going again, haha.)
RObject fill_obj = gpar_lookup(gp, "fill");
if (!fill_obj.isNULL()) {
// check fill object presence depending on type
if (fill_obj.inherits("GridPattern")) {
// gradient or pattern
have_fill_col = true;
} else {
// a string (neither a gradient nor a pattern)
CharacterVector fill(fill_obj);
if (fill.size() > 0 && !CharacterVector::is_na(fill[0])) {
have_fill_col = true;
}
}
I wonder if maybe text_grob
, rect_grob
and roundrect_grob
in grid.cpp
need to do some translation to make sure the GridPattern
lists make it back out the other side :/
Ah! But this modification does work for PNGs!
library(tidyverse)
library(ggtext)
{
ggplot(mtcars) +
aes(mpg, hp) +
geom_point() +
theme_grey(base_size = 20) +
theme(
plot.title = element_textbox(
colour = "white",
fill = grid::linearGradient(
colours = c("#020024", "#102b81", "#833ab4"),
stops = c(0, 0.4, 1)))) +
labs(title = "Hello gradients!", subtitle = "Another go")
} %>% ggsave('test_grad_title.png', ., device = png(type = "cairo"))
Maybe there's something going wrong with the PDF device when I do it this way... it definitely worked with the PDF device when I did it using grid.edit()
.
More work to do for geom_textbox
too (which makes sense to me, since there're lots of ways data columns could work as aesthetics inside a gradient):
{
ggplot(mtcars %>% rownames_to_column()) +
aes(mpg, hp) +
# geom_point() +
geom_textbox(
aes(label = rowname),
colour = "white",
fill = linearGradient(
colours = c("#020024", "#102b81", "#833ab4"),
stops = c(0, 0.4, 1))
) +
theme_grey(base_size = 20) +
theme(
plot.title = element_textbox(
colour = "white",
fill = linearGradient(
colours = c("#020024", "#102b81", "#833ab4"),
stops = c(0, 0.4, 1)))) +
labs(title = "Hello gradients!", subtitle = "Another go")
} %>% ggsave('test_grad_title2.png', ., device = png(type = "cairo"))
# Saving 6.67 x 6.67 in image
# Error: Aesthetics must be either length 1 or the same as the data (32): fill
Just FYI, you're welcome to try things out and see where you get, but please don't expect much assistance from me going forward. I'd much rather spend my limited time on the next iteration of the code base.
Very reasonable 🙂 Thanks for the heads up!
(Also adding a note to myself that this issue has been discussed in https://github.com/tidyverse/ggplot2/issues/3997 for the more general scope of ggplot2... might want to watch to see if that moves before I try to bite anything wild off with geom_textbox
).
False alarm! I was misspecifying the PDF device. This example works for both PDF and PNG:
myplot <-
ggplot(mtcars) +
aes(mpg, hp) +
geom_point() +
theme_grey(base_size = 20) +
theme(
plot.title = element_textbox(
colour = "red",
fill = grid::linearGradient(
colours = c("#020024", "#102b81", "#833ab4"),
stops = c(0, 0.4, 1)))) +
labs(title = "Hello gradients!", subtitle = "Another go")
ggsave('test_grad_title.png', myplot, device = png(type = "cairo-png"))
ggsave('test_grad_title.pdf', myplot, device = cairo_pdf)
Tested on both Linux (using the rocker/tidyverse:r-devel
image) and Windows :)
Hi Claus,
I'm thinking of having a play with R-devel to see what the new grid gradient support is like:
My naive attempt to see if this integrates with gridtext and ggtext is to just straight-up supply a
grid::linearGradient
in lieu of a colour string forfill
arguments. Do you think it's likely to be that simple, or will it require some pass-through?I understand dev is frozen on this package for now. Depending on the complexity, I might be able to submit a PR to enable this with a little guidance (or maybe I get lucky and it doesn't need any changes at all).