theming is *really* cumbersome at the moment. I would go as far as
saying it is currently the weakest aspect of ggplot2. Switching themes
completely (as is often required by journals) is not so hard with
theme_bw available. But just changing something like the orientation
of the text on a scale, which is often required (e.g. long names for
categories in x), requires to use opts and theme_text and to know the
defaults for the parameters other than orientation etc.
update_element() seems to help but it is difficult to discover.
I really think that the whole theming approach based on functions is
problematic. In my opinion, it would only add functionality if one
could use different functions for each theme element… but I don't see
how you would use theme_rect for an axis legend or theme_text for the
background of the plot. So I suggest to scratch this and replace it
with a simple hierachical named list such that theme_gray would
become:
axis.line = NA
axis.text.x = list
axis.text.x$family = "Helvetica"
axis.text.x$size = 10
axis.text.x$lineheight = 0.9
axis.text.x$colour = "grey50"
etc.
axis.text.y = list
axis.text.x$family = "Helvetica"
etc.
That way you could modify independently each element of the theme
without having to know about new functions, just by navigating a list
e.g. opts(axis.text.x$orientation=45).
This also means that users would have access to each element of the
current theme and do things like "annotate the plot with a vertical
line *the same colour as the current grid*". And this would work even
when the whole theme is changed and the grid becomes black for
example, which is currently impossible.
Combined with adding constants to the list such as the existing:
. base_size
. base_family
but also
. base_fill
. base_stroke
this would also allow to change the default colour used by ggplot own
geoms (currently black for points/lines and something like "grey20"
for fills) and make it easier to affect the whole plot.
For example, if I want all plots to have a blue-ish aspect to fit with
the design of a given piece of work, I can change the grid background
with the theme but I have to set colour/fill="myBlue" for every plot.
Cumbersome. I suspect this "complete" theming approach would prove
popular with design-nerds and this is an audience ggplot2 has interest
in pleasing (they are the ones that make ggplot look good on the web)
and almost has the means to please.
JiHO
---
http://maururu.net
I agree - when I designed it, I envisaged a completely different form
of extension than what people actually need.
> I really think that the whole theming approach based on functions is
> problematic. In my opinion, it would only add functionality if one
> could use different functions for each theme element… but I don't see
> how you would use theme_rect for an axis legend or theme_text for the
> background of the plot. So I suggest to scratch this and replace it
> with a simple hierachical named list such that theme_gray would
> become:
>
> axis.line = NA
> axis.text.x = list
> axis.text.x$family = "Helvetica"
> axis.text.x$size = 10
> axis.text.x$lineheight = 0.9
> axis.text.x$colour = "grey50"
> etc.
> axis.text.y = list
> axis.text.x$family = "Helvetica"
> etc.
>
> That way you could modify independently each element of the theme
> without having to know about new functions, just by navigating a list
> e.g. opts(axis.text.x$orientation=45).
I have a similar idea, except that I think you want less duplication:
theme$text = list(size = 12, family = "Helvetica")
theme$y = list(angle = 90)
theme$axis.text = list(size = 10)
so that axis.y.text would be assembled as list(size = 10, family =
"Helvetica", angle = 90), and you could still change the font family
of every text element with a single line of code.
> This also means that users would have access to each element of the
> current theme and do things like "annotate the plot with a vertical
> line *the same colour as the current grid*". And this would work even
> when the whole theme is changed and the grid becomes black for
> example, which is currently impossible.
It's not obvious to me how that would work. Within a geom, you'd need
some way of delaying evaluation of the aesthetic until the plot was
rendered. This tends to be complex and bug-prone.
> Combined with adding constants to the list such as the existing:
> . base_size
> . base_family
> but also
> . base_fill
> . base_stroke
> this would also allow to change the default colour used by ggplot own
> geoms (currently black for points/lines and something like "grey20"
> for fills) and make it easier to affect the whole plot.
See above, but themes are explicitly tied to non-data elements.
Technically, allowing themes to affect the data-elements would be
complex, and, I suspect, confusing (because it would become
increasingly hard to figure out why your points were blue).
> For example, if I want all plots to have a blue-ish aspect to fit with
> the design of a given piece of work, I can change the grid background
> with the theme but I have to set colour/fill="myBlue" for every plot.
> Cumbersome. I suspect this "complete" theming approach would prove
> popular with design-nerds and this is an audience ggplot2 has interest
> in pleasing (they are the ones that make ggplot look good on the web)
> and almost has the means to please.
I agree that this would be nice, but I don't see any easy way of
implementing it.
Hadley
--
Assistant Professor / Dobelman Family Junior Chair
Department of Statistics / Rice University
http://had.co.nz/
OK. Being able to change the font with one line of code is indeed
necessary and your way is much more versatile and clever than my
suggestion of using using base_family, base_size etc.
But you could mix the content of those elements however you want? Such as:
theme$text = list(size = 12, family = "Helvetica")
theme$y = list(angle = 90)
theme$axis.text = list(size = 10)
theme$x = list(size = 15)
In that case, how would you decide on the size of the text on the x
axis? Does axis.text take precedence or x?
>> This also means that users would have access to each element of the
>> current theme and do things like "annotate the plot with a vertical
>> line *the same colour as the current grid*". And this would work even
>> when the whole theme is changed and the grid becomes black for
>> example, which is currently impossible.
>
> It's not obvious to me how that would work. Within a geom, you'd need
> some way of delaying evaluation of the aesthetic until the plot was
> rendered. This tends to be complex and bug-prone.
I did not think about the technical aspect of this of course. But
currently, the theme used by a plot is the one that was in place when
the plot was *constructed*, not when the plot is rendered, right? This
would be the same: at the time the plot is constructed, fetch the
current grid colour and use that in the layer of the plot.
By "this would work even when the whole theme is changed" I don't mean
changing the theme on the fly, I mean writing some code with the
default theme, realizing the journal/presentation requires something
else and regenerating the plots with a new theme.
Currently, if we keep the example of the grid colour I could do
d = data.frame(x=runif(10), y=runif(10)-0.5)
ggplot(d) +
geom_hline(yintercept=0, size=2, colour="white") +
geom_point(aes(x, y))
to emphasize 0 in the y direction. Now if I do:
theme_set(theme_bw(12))
d = data.frame(x=runif(10), y=runif(10)-0.5)
ggplot(d) +
geom_hline(yintercept=0, size=2, colour="white") +
geom_point(aes(x, y))
this does not work anymore, I must change it to:
ggplot(d) +
geom_hline(yintercept=0, size=2, colour="grey30") +
geom_point(aes(x, y))
What I would have preferred to write would have been something like:
ggplot(d) +
geom_hline(yintercept=0, size=2, colour=theme$major$colour) +
geom_point(aes(x, y))
>> Combined with adding constants to the list such as the existing:
>> . base_size
>> . base_family
>> but also
>> . base_fill
>> . base_stroke
>> this would also allow to change the default colour used by ggplot own
>> geoms (currently black for points/lines and something like "grey20"
>> for fills) and make it easier to affect the whole plot.
>
> See above, but themes are explicitly tied to non-data elements.
> Technically, allowing themes to affect the data-elements would be
> complex,
I probably have a very bad idea of how these things work internally
but, assuming that my reasonning above is correct (theme is evaluted
when the plot is built), using something like
theme$base_fill/stroke/colour (or theme$content_colour) as the default
in all geoms would simply get replaced by the content of this variable
(which is ensured to exist because a theme is loaded when ggplot is
loaded) when the plot is built. And then this would be rendered as any
other colour.
> and, I suspect, confusing (because it would become
> increasingly hard to figure out why your points were blue).
I see where you are going with the possible confusion, theoretically.
But I don't think it would be very different, in the end, to setting
colour="blue" in the geom call, and that does not seem to be a
problem.
Currently points are black on a grey background and this is natural
because black and grey aren't really colours. But I think that blue
points on a blue background would give the same impression. See this
for an example
http://dl.dropbox.com/u/1047321/ggplot/Screen%20Shot%202012-01-19%20at%2023.46.08%20.png
>> For example, if I want all plots to have a blue-ish aspect to fit with
>> the design of a given piece of work, I can change the grid background
>> with the theme but I have to set colour/fill="myBlue" for every plot.
>> Cumbersome. I suspect this "complete" theming approach would prove
>> popular with design-nerds and this is an audience ggplot2 has interest
>> in pleasing (they are the ones that make ggplot look good on the web)
>> and almost has the means to please.
>
> I agree that this would be nice, but I don't see any easy way of
> implementing it.
Again, I thought that having a defaut colour in the theme that would
exist before any plot is created and be evaluated when the plot is
built would work. When it would come to displaying the plot the colour
would be already coded as "#4A88FE" and not theme$base_colour anymore.
But of course it might be (probably is) more complicated.
JiHO
---
http://maururu.net
Yeah, the the challenge would be getting the precedence rules right.
In this case I think it's simple - axis.text would take precedence
because it's more specific. A more challenging example would be
theme$axis.text = list(size = 10)
theme$text.x = list(size = 12)
theme$axis.x = list(size = 14)
which is rather contrived, but we'd still need a general rule to
handle it. Maybe something like: the if the theme item is
axis.text.x, then axis would get 4 points, text 2 points and x 1.
Rules would be applied in reverse order of their points, so that
axis.text.x would have the highest precedence, followed by axis.text,
then axis.x, then text.x. Order of elements wouldn't matter.
> I did not think about the technical aspect of this of course. But
> currently, the theme used by a plot is the one that was in place when
> the plot was *constructed*, not when the plot is rendered, right? This
> would be the same: at the time the plot is constructed, fetch the
> current grid colour and use that in the layer of the plot.
Nope:
p <- qplot(mpg, wt, data = mtcars)
ggtheme(theme_bw())
p
> By "this would work even when the whole theme is changed" I don't mean
> changing the theme on the fly, I mean writing some code with the
> default theme, realizing the journal/presentation requires something
> else and regenerating the plots with a new theme.
> Currently, if we keep the example of the grid colour I could do
>
> d = data.frame(x=runif(10), y=runif(10)-0.5)
> ggplot(d) +
> geom_hline(yintercept=0, size=2, colour="white") +
> geom_point(aes(x, y))
>
> to emphasize 0 in the y direction. Now if I do:
>
> theme_set(theme_bw(12))
> d = data.frame(x=runif(10), y=runif(10)-0.5)
> ggplot(d) +
> geom_hline(yintercept=0, size=2, colour="white") +
> geom_point(aes(x, y))
>
> this does not work anymore, I must change it to:
>
> ggplot(d) +
> geom_hline(yintercept=0, size=2, colour="grey30") +
> geom_point(aes(x, y))
>
> What I would have preferred to write would have been something like:
>
> ggplot(d) +
> geom_hline(yintercept=0, size=2, colour=theme$major$colour) +
> geom_point(aes(x, y))
Which you can basically do already, with get("colour",
environment(theme_get()$panel.grid.major) - the syntax could easily be
improved, but it's much harder to get the theme information from when
the plotted is rendered, not created.
> I probably have a very bad idea of how these things work internally
> but, assuming that my reasonning above is correct (theme is evaluted
> when the plot is built), using something like
> theme$base_fill/stroke/colour (or theme$content_colour) as the default
> in all geoms would simply get replaced by the content of this variable
> (which is ensured to exist because a theme is loaded when ggplot is
> loaded) when the plot is built. And then this would be rendered as any
> other colour.
Yes, but it doesn't work that way :/
OK. Not trivial indeed. Especially since in theme$axis.x = list(size =
14) the size could also be the linesize of the axis right?
>> I did not think about the technical aspect of this of course. But
>> currently, the theme used by a plot is the one that was in place when
>> the plot was *constructed*, not when the plot is rendered, right?
>
> Nope:
>
> p <- qplot(mpg, wt, data = mtcars)
> ggtheme(theme_bw())
> p
I don't have ggtheme even in my dev version, but theme_set works and
indeed the theme used is the one active at rendering time. And it's
even spelled out explicitly in the book. I don't know why I had the
wrong idea (I thought theme_set and theme_update were different in
this respect for some reason).
So of course all the rest of my suggestions become obsolete because it
would be impossible (or at least very difficult) to have the plot
fetch a colour at rendering time (at the time of creation, the colour
would have probably to be a function that gets executed at rendering
time to fetch the colour... nightmare).
Sorry to have bothered you then :-/
JiHO
---
http://maururu.net
Could there be global, mutable list of options accessed throughout
ggplot2? Lattice uses lattice.options and trellis.par.get for a
similar purpose (however they are not modified by the "layers" = panel
functions).
Some object, perhaps using reference classes, would hold a set of
global parameters.
To organize these parameters in a coherent, legible manner, we could
draw inspiration from Beamer. Beamer defines various themes: Outer
Themes, Inner Themes, Color Themes, Font Themes. This provides a
flexible interface where each element has a default value that is
consistent with the rest of the plot (inherits from its parent's
attributes), yet can still be modified at the individual level. This
needs to be dynamic, ideally almost at rendering time.
A note on colours: Beamer defines a foreground "fg" and a background
"bg" for each element (some of them might not need it).
Best,
baptiste
> --
> You received this message because you are subscribed to the ggplot2 mailing list.
> Please provide a reproducible example: http://gist.github.com/270442
>
> To post: email ggp...@googlegroups.com
> To unsubscribe: email ggplot2+u...@googlegroups.com
> More options: http://groups.google.com/group/ggplot2
There already is? See theme_set/theme_get.
> To organize these parameters in a coherent, legible manner, we could
> draw inspiration from Beamer. Beamer defines various themes: Outer
> Themes, Inner Themes, Color Themes, Font Themes. This provides a
> flexible interface where each element has a default value that is
> consistent with the rest of the plot (inherits from its parent's
> attributes), yet can still be modified at the individual level. This
> needs to be dynamic, ideally almost at rendering time.
Which is what opts gives you?
I have to admit that I never could wrap my head around ggplot2 themes. See
below for elaboration.
On Friday, March 02, 2012 11:58:13 AM Hadley Wickham wrote:
> > Could there be global, mutable list of options accessed throughout
> > ggplot2? Lattice uses lattice.options and trellis.par.get for a
> > similar purpose (however they are not modified by the "layers" = panel
> > functions).
>
> There already is? See theme_set/theme_get.
theme_get/set/update confuse the hell out of me. To see why, take a look at
this annotated version of the function examples (from ?theme_update):
##################################################
library(ggplot2)
## plot
qplot(mpg, wt, data = mtcars)
# plot rendered using default theme_gray()
old <- theme_set(theme_bw())
qplot(mpg, wt, data = mtcars)
## Plot is rendered using theme_bw().
## OK, makes sense since we set the theme to theme_bw()
theme_set(old)
qplot(mpg, wt, data = mtcars)
## Plot is rendered using theme_gray().
## What!?!? where did that come from?
## We said "old <- theme_set(theme_bw())",
## so why did old become theme_gray()?
old <- theme_update(panel.background = theme_rect(colour = "pink"))
qplot(mpg, wt, data = mtcars)
## Plot is rendered with pink panel.background,
## as intended. But everything else changes too!
## I thought old = theme_gray (even though I
## didn't understand why...), but now I see that
## changing the panel.background somehow magically
## made old = theme_bw() with pink panel.background.
## Why did the previous theme_set(old) switch back
## to theme_gray(), but theme_update() updates theme_bw()???
theme_set(old)
qplot(mpg, wt, data = mtcars)
## Theme is now back to theme_gray(),
## no pink panel.background! why?!?!
## The way theme_set/get/update works is
## so mysterious to me that I don't even use
## them. Instead I use
my_theme <- function (base_size = 12, base_family = "", ...){
modifyList (...)}
## following the examples on the wiki.
## This is a little cumbersome, but at
## least it seems consistent and understandable.
##################################################
I hope this example of my confusion might a) prompt somone to explain how this
stuff is supposed to work, and/or b) provide some things to consider if the
themeing system is re-worked.
Best,
Ista
Your confusion seems to be around the return value of theme_set():
function (new)
{
# [.. snip ..]
old <- theme
theme <<- new
invisible(old)
}
It works like par() in base graphics: it invisibly changes the theme
(<<- operator), and returns the old theme in case you need it later.
baptiste
PS: I'm hoping to create an example of a theming system that I had in
mind using ref classes (I also need one for a new tableGrob), but it
might take a while.
On Monday, March 05, 2012 08:04:02 AM baptiste auguie wrote:
> Hi,
>
> Your confusion seems to be around the return value of theme_set():
>
> function (new)
> {
> # [.. snip ..]
> old <- theme
> theme <<- new
> invisible(old)
> }
Yes, that is a source of my confusion, but (I think) not the only one. There
is also the issue of
theme_update(panel.background = theme_rect(colour = "pink"))
changing more than just the panel.background
>
> It works like par() in base graphics: it invisibly changes the theme
> (<<- operator), and returns the old theme in case you need it later.
An important difference between par() and theme_set() is that ?par tells you
that "previous values are returned in an invisible named list". ?theme_set
doesn't say anything about it's return value.
Best,
Ista
what else does it change? (note the default fill of theme_rect)
b.
Ah, I see. Thanks for de-mystifying this for me! At the end of the day I think
the lack of documentation is the biggest problem with the way ggplot2 handles
themes.
Best,
Ista