Repository: altair-viz/altair

Issue:
ENH: allow specification of embed options from chart.display()
See https://github.com/altair-viz/altair/issues/688#issuecomment-395523194

Discussion:

Patch:
diff --git a/altair/utils/plugin_registry.py b/altair/utils/plugin_registry.py
index 3c57e1af..ad76662f 100644
--- a/altair/utils/plugin_registry.py
+++ b/altair/utils/plugin_registry.py
@@ -138,7 +138,7 @@ class PluginRegistry(Generic[PluginType]):
         self._active = self._plugins[name]
         self._options = options
 
-    def enable(self, name, **options):
+    def enable(self, name=None, **options):
         # type: (str, **Any) -> PluginEnabler
         """Enable a plugin by name.
 
@@ -146,8 +146,9 @@ class PluginRegistry(Generic[PluginType]):
 
         Parameters
         ----------
-        name : string
-            The name of the plugin to enable
+        name : string (optional)
+            The name of the plugin to enable. If not specified, then use the
+            current active name.
         **options :
             Any additional parameters will be passed to the plugin as keyword
             arguments
@@ -157,7 +158,10 @@ class PluginRegistry(Generic[PluginType]):
         PluginEnabler:
             An object that allows enable() to be used as a context manager
         """
+        if name is None:
+            name = self.active
         return PluginEnabler(self, name, **options)
+        
 
     @property
     def active(self):
diff --git a/altair/vegalite/v2/api.py b/altair/vegalite/v2/api.py
index 7ef6b2a9..e8a5ed26 100644
--- a/altair/vegalite/v2/api.py
+++ b/altair/vegalite/v2/api.py
@@ -915,10 +915,42 @@ class TopLevelMixin(mixins.ConfigMethodMixin):
         else:
             return renderers.get()(dct)
 
-    def display(self):
-        """Display chart in Jupyter notebook or JupyterLab"""
+    def display(self, renderer=Undefined, theme=Undefined, actions=Undefined,
+                **kwargs):
+        """Display chart in Jupyter notebook or JupyterLab
+
+        Parameters are passed as options to vega-embed within supported frontends.
+        See https://github.com/vega/vega-embed#options for details.
+
+        Parameters
+        ----------
+        renderer : string ('canvas' or 'svg')
+            The renderer to use
+        theme : string
+            The Vega theme name to use; see https://github.com/vega/vega-themes
+        actions : bool or dict
+            Specify whether action links ("Open In Vega Editor", etc.) are
+            included in the view.
+        **kwargs :
+            Additional parameters are also passed to vega-embed as options.
+        """
         from IPython.display import display
-        display(self)
+
+        if renderer is not Undefined:
+            kwargs['renderer'] = renderer
+        if theme is not Undefined:
+            kwargs['theme'] = theme
+        if actions is not Undefined:
+            kwargs['actions'] = actions
+
+        if kwargs:
+            options = renderers.options.copy()
+            options['embed_options']= options.get('embed_options', {}).copy()
+            options['embed_options'].update(kwargs)
+            with renderers.enable(**options):
+                display(self)
+        else:
+            display(self)
 
     def serve(self, ip='127.0.0.1', port=8888, n_retries=50, files=None,
               jupyter_warning=True, open_browser=True, http_server=None,
diff --git a/doc/user_guide/configuration.rst b/doc/user_guide/configuration.rst
index 934e0275..dee77175 100644
--- a/doc/user_guide/configuration.rst
+++ b/doc/user_guide/configuration.rst
@@ -295,15 +295,6 @@ fill unless otherwise specified:
         y='Miles_per_Gallon:Q'
     )
 
-
-If you want to restore the default theme, use:
-
-.. altair-plot::
-   :output: none
-
-   alt.themes.enable('default')
-	    
-
 For more ideas on themes, see the `Vega Themes`_ repository.
 
 


Test patch:
diff --git a/altair/utils/tests/test_plugin_registry.py b/altair/utils/tests/test_plugin_registry.py
index 3b3d2945..6aa785e9 100644
--- a/altair/utils/tests/test_plugin_registry.py
+++ b/altair/utils/tests/test_plugin_registry.py
@@ -41,8 +41,14 @@ def test_plugin_registry_extra_options():
     assert plugins.get()(3) == 9
 
     plugins.enable('metadata_plugin', p=3)
+    assert plugins.active == 'metadata_plugin'
     assert plugins.get()(3) == 27
 
+    # enabling without changing name
+    plugins.enable(p=2)
+    assert plugins.active == 'metadata_plugin'
+    assert plugins.get()(3) == 9
+
 
 def test_plugin_registry_context():
     plugins = GeneralCallableRegistry()
@@ -72,3 +78,13 @@ def test_plugin_registry_context():
 
     assert plugins.active == ''
     assert plugins.options == {}
+
+    # Enabling without specifying name uses current name
+    plugins.enable('default', p=2)
+
+    with plugins.enable(p=6):
+        assert plugins.active == 'default'
+        assert plugins.options == {'p': 6}
+
+    assert plugins.active == 'default'
+    assert plugins.options == {'p': 2}
diff --git a/altair/vegalite/v2/tests/test_display.py b/altair/vegalite/v2/tests/test_display.py
new file mode 100644
index 00000000..80dce231
--- /dev/null
+++ b/altair/vegalite/v2/tests/test_display.py
@@ -0,0 +1,63 @@
+from contextlib import contextmanager
+
+import pytest
+
+import altair as alt
+
+
+@contextmanager
+def check_render_options(**options):
+    """
+    Context manager that will assert that alt.renderers.options are equivalent
+    to the given options in the IPython.display.display call
+    """
+    import IPython.display
+    def check_options(obj):
+        assert alt.renderers.options == options
+    _display = IPython.display.display
+    IPython.display.display = check_options
+    try:
+        yield
+    finally:
+        IPython.display.display = _display
+
+
+def test_check_renderer_options():
+    # this test should pass
+    with check_render_options():
+        from IPython.display import display
+        display(None)
+
+    # check that an error is appropriately raised if the test fails
+    with pytest.raises(AssertionError):
+        with check_render_options(foo='bar'):
+            from IPython.display import display
+            display(None)
+
+
+def test_display_options():
+    chart = alt.Chart('data.csv').mark_point().encode(x='foo:Q')
+
+    # check that there are no options by default
+    with check_render_options():
+        chart.display()
+
+    # check that display options are passed
+    with check_render_options(embed_options={'tooltip': False, 'renderer': 'canvas'}):
+        chart.display('canvas', tooltip=False)
+
+    # check that above options do not persist
+    with check_render_options():
+        chart.display()
+
+    # check that display options augment rather than overwrite pre-set options
+    with alt.renderers.enable(embed_options={'tooltip': True, 'renderer': 'svg'}):
+        with check_render_options(embed_options={'tooltip': True, 'renderer': 'svg'}):
+            chart.display()
+
+        with check_render_options(embed_options={'tooltip': True, 'renderer': 'canvas'}):
+            chart.display('canvas')
+
+    # check that above options do not persist
+    with check_render_options():
+        chart.display()