Repository: vega/altair

Issue:
Support built-in vega themes
See [vega-themes](https://github.com/vega/vega-themes). Themes should be supported via the current theme infrastructure, maybe something like this:
```python
alt.themes.enable('vega.themes.dark')
```
We'll have to think about how to best populate the list of available themes, and how to make this work cleanly with user-specified themes from within Altair.


Discussion:
Possibly this could be done via a pip-installable ``vega_themes`` package that uses entrypoints to define available themes...
I just released vega3 0.11.0 with the latest Vega-Embed, which has support for themes. See https://github.com/vega/vega-embed#options
I made a little demo at https://beta.observablehq.com/@domoritz/vega-themes-demo. The themes are definitely not tuned yet but having the infrastructure in place will be useful in the future and I hope that we get people to help with improving the themes.
I just ported the LA Times theme to Altair so that I could use it in a conference presentation. https://github.com/AlgorexHealth/ACO-PUF-Analysis/blob/master/ACO%20Results.ipynb I could take a stab at this. 

**Some Options**:
It does seem that having a pip installable "vega_themes" would be the way to go. 

1. Could simply copy the themes from the typescript source into python dictionaries stored in source files. This is what I did for the LA times theme it took about 10 minutes for each theme. 

2. Should be pretty easy to write some typescript that would consume the existing vega-themes source code and serialize all of the themes into JSON files that python could load and make available to Altair. 

I tried this quickly simply running `JSON.stringify` on one of the themes and this worked pretty well. 

```
{"background":"#333","title":{"color":"#fff"},"style":{"guide-label":{"fill":"#fff"},"guide-title":{"fill":"#fff"}},"axis":{"domainColor":"#fff","gridColor":"#888","tickColor":"#fff"}}
```

Either @domoritz or @jakevdp might have a much better idea for consuming these so that we can allow all theme changes to be made in the existing repository and have altair just consume them or at the very least point to  new version. 
As noted elsewhere (I can't find the reference now) it's also possible to use vega themes directly in Altair, by running e.g.
```python
import altair as alt
alt.renderers.set_embed_options(theme='latimes')
```
I prefer setting the theme in Vega-Embed so that users always get the latest version of the themes. Maybe Altair can have a convenience wrapper to support what @jakevdp showed above. 
I was just in the process of editing my comment to include that option. 

@jakevdp I will send up a PR to add that to the documentation. 

I stumbled on this github issue and didn't get that context so I assumed this wasn't possible. 
Unfortunately the mechanisms used by Altair themes and by Vega themes are entirely orthogonal... I've put a bit of thought into how they might be unified into a single user interface in Altair, but there's really no clean way to do it that I can see currently.

The problem is that in Altair, themes are controlled via the chart spec, while in Vega-Lite, themes are controlled via javascript arguments the vega-embed library's interface. In my opinion, I think the latter should change: themes affect the appearance of the chart, and thus enabling or disabling them should be part of the chart specification. If Vega-Lite were to make that change (perhaps with a ``"config": "theme"`` setting) then the API for Altair and Vega themes could be unified.
As a concrete example of why the current vega theme implementation is suboptimal, consider a world in which other means of rendering Vega-Lite specs are created (for example, you could imagine a matplotlib renderer for a vega-lite spec). You could pass your spec to the other renderer, but where do you pass the theme name? It's not part of the chart spec, so the answer is not obvious. Perhaps there could be some optional args to whatever python function does the matplotlib rendering, similar to how vega-embed takes optional args to control the theme.

But at that point, we've admitted that Vega-Lite no longer provides a self-contained declarative representation of the chart we want to create.
Ok, I'll leave it alone and if anyone finds this thread they will see the solution if they simply want to use a vega theme on render. 


@jakevdp I think we could add an annotation to Vega and Vega-Lite about what theme to load but Vega and Vega-Lite would by default ignore the annotation. Vega-Embed could then be able to read it and load the appropriate theme. How does that sound?
I think that would be a useful addition.
In fact, we could support this easily with `usermeta`. 

```json
{
  "$schema": "https://vega.github.io/schema/vega-lite/v3.json",
  "description": "A simple bar chart with embedded data.",
  "usermeta": {
    "embedOptions": {
      "theme": "dark"
    }
  },
  "data": {
    "values": [
      {"a": "A","b": 28}, {"a": "B","b": 55}, {"a": "C","b": 43},
      {"a": "D","b": 91}, {"a": "E","b": 81}, {"a": "F","b": 53},
      {"a": "G","b": 19}, {"a": "H","b": 87}, {"a": "I","b": 52}
    ]
  },
  "mark": "bar",
  "encoding": {
    "x": {"field": "a", "type": "ordinal"},
    "y": {"field": "b", "type": "quantitative"}
  }
}
```

I could update Vega-Embed to look for `usermeta.embedOptions`. 
In that case on the Altair side, would it be as simple as adding something like this to vegalite/v3/theme.py and the others?

```
themes.register(‘latimes', lambda: {“usermeta": {“embedOptions": {“theme”:”latimes”}}}

```





> On May 15, 2019, at 12:47 PM, Dominik Moritz <notifications@github.com> wrote:
> 
> In fact, we could support this easily with usermeta.
> 
> {
>   "$schema": "https://vega.github.io/schema/vega-lite/v3.json",
>   "description": "A simple bar chart with embedded data.",
>   "usermeta": {
>     "embedOptions": {
>       "theme": "dark"
>     }
>   },
>   "data": {
>     "values": [
>       {"a": "A","b": 28}, {"a": "B","b": 55}, {"a": "C","b": 43},
>       {"a": "D","b": 91}, {"a": "E","b": 81}, {"a": "F","b": 53},
>       {"a": "G","b": 19}, {"a": "H","b": 87}, {"a": "I","b": 52}
>     ]
>   },
>   "mark": "bar",
>   "encoding": {
>     "x": {"field": "a", "type": "ordinal"},
>     "y": {"field": "b", "type": "quantitative"}
>   }
> }
> I could update Vega-Embed to look for usermeta.embedOptions.
> 
> —
> You are receiving this because you commented.
> Reply to this email directly, view it on GitHub <https://github.com/altair-viz/altair/issues/781?email_source=notifications&email_token=AE6I5MD4EONV63HDXOLMDG3PVQ5BNA5CNFSM4E4RYZI2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGODVPIHZI#issuecomment-492733413>, or mute the thread <https://github.com/notifications/unsubscribe-auth/AE6I5MB43EACCN6CGHKZC4DPVQ5BNANCNFSM4E4RYZIQ>.
> 


> I think we could add an annotation to Vega and Vega-Lite about what theme to load but Vega and Vega-Lite would by default ignore the annotation.

What is the rational for that? Wouldn't it be much easier if the theme choice is just part of the spec properly?

> In fact, we could support this easily with usermeta

I think I would prefer something less obscure, why not go with @jakevdp's suggestion of `"config": "theme"`?
Update: Vega-Embed now supports `userMeta.embedOptions`. 

> What is the rational for that? Wouldn't it be much easier if the theme choice is just part of the spec properly?

Themes are still developing and have not reached the maturity of Vega and Vega-Lite. You can always bake a theme into the spec as a config. Right now, Vega and Vega-Lite have no dependency on Vega-Themes and I think that's a good thing at this point. 

> I think I would prefer something less obscure, why not go with @jakevdp's suggestion of "config": "theme"?

`usermeta` is already a supported property in Vega and Vega-Lite so I put it in there. `"config": "theme"` would require updates to Vega and Vega-Lite and also is less flexible compared to supporting all embed options. 
Awesome, thanks Dom! I'll put together a PR for enabling vega themes via the existing Altair themes settings.

Resolving patch:
diff --git a/altair/vegalite/v3/theme.py b/altair/vegalite/v3/theme.py
--- a/altair/vegalite/v3/theme.py
+++ b/altair/vegalite/v3/theme.py
@@ -2,6 +2,23 @@
 
 from ...utils.theme import ThemeRegistry
 
+VEGA_THEMES = ['ggplot2', 'quartz', 'vox', 'fivethirtyeight', 'dark', 'latimes']
+
+
+class VegaTheme(object):
+    """Implementation of a builtin vega theme."""
+    def __init__(self, theme):
+        self.theme = theme
+        
+    def __call__(self):
+        return {"usermeta": {"embedOptions": {"theme": self.theme}},
+                "config": {"view": {"width": 400, "height": 300},
+                           "mark": {"tooltip": None}}}
+
+    def __repr__(self):
+        return "VegaTheme({!r})".format(self.theme)
+
+
 # The entry point group that can be used by other packages to declare other
 # renderers that will be auto-detected. Explicit registration is also
 # allowed by the PluginRegistery API.
@@ -14,4 +31,8 @@
                                               "view": {"width": 400, "height": 300},
                                               "mark": {"tooltip": None}}})
 themes.register('none', lambda: {})
+  
+for theme in VEGA_THEMES:
+    themes.register(theme, VegaTheme(theme))
+
 themes.enable('default')


Test patch:
diff --git a/altair/vegalite/v3/tests/test_theme.py b/altair/vegalite/v3/tests/test_theme.py
new file mode 100644
--- /dev/null
+++ b/altair/vegalite/v3/tests/test_theme.py
@@ -0,0 +1,17 @@
+import pytest
+
+import altair.vegalite.v3 as alt
+from altair.vegalite.v3.theme import VEGA_THEMES
+
+
+@pytest.fixture
+def chart():
+    return alt.Chart('data.csv').mark_bar().encode(x='x:Q')
+
+def test_vega_themes(chart):
+    for theme in VEGA_THEMES:
+        with alt.themes.enable(theme):
+            dct = chart.to_dict()
+        assert dct['usermeta'] == {'embedOptions': {'theme': theme}}
+        assert dct['config'] == {"view": {"width": 400, "height": 300},
+                                 "mark": {"tooltip": None}}