Closed flying-sheep closed 6 years ago
The way that ggplot works, the default colors are set by Geoms, not by themes. So there's no way to do what you request. However, this is one reason why ggthemes makes the colors used in the themes available as data in the object ggthemes_data
. The colors used in the solarized themes and scales are in ggthemes_data$solarized
. For example,
d <- data.frame(x = 1:10, y = (1:10)^2, s = as.character(1:10 < 6))
ggplot(d, aes(x, y, shape = s)) + geom_point(colour = ggthemes_data$solarized$base['base1']) + theme_solarized(light = FALSE)
hmm, so if i do ggplot(d, aes(x, y, shape = s, colour = c)) + geom_point()
, even if the geom color isn’t used at all, i still need to set it to get a sensible color in the legend?
that is, unless i use update_geom_defaults
for a global change
If you do ggplot(d, aes(x, y, shape = s, colour = c))
then color is an aesthetic mapped to the variable c
and the colors themselves are determined by a scale. You use a color
argument to a geom in order to directly set its value to a
You could also use update_geom_defaults
, but since that applies globally and acts at a different time, it needs to be handled separately from the theme. I don't like changing the state anways, since that often leads to harder to debug bugs.
yes, i know. my point is that in the shape legend, the point color will still be black instead of being taken from the theme or the color scale.
i’d need to do this if i wanted to have something pretty, which is non-obvious and goes against the idea of the “grammar of graphics”:
ggplot(d, aes(x, y, shape = s, colour = c)) +
# this color is just for the legend:
geom_point(colour = ggthemes_data$solarized$base[['base1']])
The problem is that in general you can't get assured that you will get reasonable values for geom colors from themes. Some themes may set all background values to be blank. For example, theme_void
.
who’s talking about backgrounds?
> sapply(theme_classic()[c('line', 'rect', 'text')], function(e) e$colour)
line rect text
"black" "black" "black"
but in any case: then we can still fall back to some value.
basically:
ATM this is my (incomplete) workaround:
apply_theme <- function(gg, t) {
for (l in seq_along(gg$layers)) {
gg$layers[[l]]$geom$default_aes$colour <- t$text$colour
}
gg + t
}
That's interesting. How about using update_geom_defaults
instead? Personally, I like to avoid changing the state, but it seems an okay idea (at least one that ggplot2 has a built-in way to do). If you implement it, make a pull request and I'll incorporate it.
My own approach would use something like purrr::partial
to return a new geom function from an old geom function with new defaults set.
we could do something withr
-style, like:
with_theme <- function(theme, expr) {
old_col <- GeomPoint$default_aes$colour
tryCatch({
update_geom_defaults('point', list(colour = get_primary_colour('point', theme)))
expr + theme
}, finally = {
update_geom_defaults('point', list(colour = old_col))
})
}
where get_primary_colour
gets a color for a theme and geom or falls back to the default (Geom*$default_aes$colour
)
My own approach would use something like
purrr::partial
to return a new geom function from an old geom function with new defaults set.
ultimately this issue is about working around a ggplot2 shortcoming. I think partial
would be the way to go if we’d deal with optimal design (like most other parts of ggplot2), but as we’re doing a workaround, i think practicality beats purity and we should just wrap the whole expression instead of each individual geom call.
hmm, doesn’t work though. if i define with_theme
as above and create a dummy get_primary_colour <- function(geom, theme) theme$text$colour
then GeomPoint$default_aes$colour
is correct at the time of the evaluation of expr
but it doesn’t end up in the layer.
In[1]: gg <- with_theme(theme_solarized_2(light = FALSE), {
...: cat(GeomPoint$default_aes$colour)
...: qplot(1:10, (1:10)^2)
...: })
"#586e75"
In[2]: cat(gg$layers[[1]]$default_aes$colour)
"black"
any ideas?
an alternative to my apply_theme
above:
`%+theme%` <- function(gg, theme) {
gg <- gg + theme
for (l in seq_along(gg$layers)) {
gg$layers[[l]]$geom$default_aes$colour <- theme$text$colour
}
gg
}
qplot(1:10, (1:10)^2) %+theme% theme_solarized_2(light = FALSE)
%+theme
looks nice! Should it only change colour
or should it also change fill
? And how about two functions. One that changes update_geom_defaults
for those that want it, and the %+theme
for temporary changes?
Make a PR and incorporate it.
i guess we should make it dependent on if the defaults are set. are there geoms with a default fill
?
All the geoms have all their default aesthetics set in their ggproto object, e.g. geom_ribbon
hmm, that makes it a bit harder.
so we have to do something like this (or even more fine-grained)
for (aesthetic in c('fill', 'colour')) {
default <- gg$layers[[l]]$geom$default_aes[[aesthetic]]
gg$layers[[l]]$geom$default_aes[[aesthetic]] <-
switch(default, grey20 = get_soft_colour(theme), black = get_primary_colour(theme))
}
now we only need get_soft_colour
and get_primary_colour
.
my idea would be to try for a few colors that can be set in a theme. e.g. for the soft color, try all grid colours that could be set. any suggestions for an order for primary and soft colors?
question to myself for later: are there more than grey20
and black
defined as default?
I think it was that sort of issue that kept me from implementing something like this in the first place. That it is one of those problems that seems like it should be easy and obvious, but there are a bunch of annoying edge cases. Which is why my initial "solution" was to expose the colors for the themes through ggthemes_data
to allow allow for the manual use of it.
I think you can get something that works 80% of the time, which is big time saver. But I would guess that it'll be annoying for those last percentage points.
Going back to the idea of a closure, how about a function that applies specific values from the theme
themed_geom_point() <- apply_theme(geom_point, theme_solarized(), colour = c("text", "colour"))
(Sorry: I didn't have time to figure out the body of the function). Then this can be extended with smart defaults, or to apply to multiple geoms, or also work through update_geom_defaults
.
Regarding the default aes: off the top of my head geom_smooth
uses blue for the line.
hi! did you implement anything to make this easier or why did you close it?