Repository: vega/altair

Issue:
ENH: chart.serve()
I've been getting repeated requests from people who want to use Altair, but are deathly allergic to notebook environments.

Altair 1.0 had ``chart.serve()`` for these poor souls. We should bring it back. The mechanics are still in https://github.com/altair-viz/altair/blob/master/altair/utils/server.py


Discussion:

Resolving patch:
diff --git a/altair/vegalite/v1/api.py b/altair/vegalite/v1/api.py
--- a/altair/vegalite/v1/api.py
+++ b/altair/vegalite/v1/api.py
@@ -251,6 +251,50 @@ def _repr_mimebundle_(self, include, exclude):
         else:
             return renderers.get()(dct)
 
+    def display(self):
+        """Display chart in Jupyter notebook or JupyterLab"""
+        from IPython.display import display
+        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,
+              **kwargs):
+        """Open a browser window and display a rendering of the chart
+
+        Parameters
+        ----------
+        html : string
+            HTML to serve
+        ip : string (default = '127.0.0.1')
+            ip address at which the HTML will be served.
+        port : int (default = 8888)
+            the port at which to serve the HTML
+        n_retries : int (default = 50)
+            the number of nearby ports to search if the specified port
+            is already in use.
+        files : dictionary (optional)
+            dictionary of extra content to serve
+        jupyter_warning : bool (optional)
+            if True (default), then print a warning if this is used
+            within the Jupyter notebook
+        open_browser : bool (optional)
+            if True (default), then open a web browser to the given HTML
+        http_server : class (optional)
+            optionally specify an HTTPServer class to use for showing the
+            figure. The default is Python's basic HTTPServer.
+        **kwargs :
+            additional keyword arguments passed to the save() method
+        """
+        from ...utils.server import serve
+
+        html = six.StringIO()
+        self.save(html, format='html', **kwargs)
+        html.seek(0)
+
+        serve(html.read(), ip=ip, port=port, n_retries=n_retries,
+              files=files, jupyter_warning=jupyter_warning,
+              open_browser=open_browser, http_server=http_server)
+
 
 class Chart(TopLevelMixin, core.ExtendedUnitSpec):
     def __init__(self, data=Undefined, encoding=Undefined, mark=Undefined,
diff --git a/altair/vegalite/v2/api.py b/altair/vegalite/v2/api.py
--- a/altair/vegalite/v2/api.py
+++ b/altair/vegalite/v2/api.py
@@ -421,20 +421,6 @@ def __and__(self, other):
     def __or__(self, other):
         return HConcatChart(hconcat=[self, other])
 
-    # Display-related methods
-
-    def _repr_mimebundle_(self, include, exclude):
-        """Return a MIME bundle for display in Jupyter frontends."""
-        # Catch errors explicitly to get around issues in Jupyter frontend
-        # see https://github.com/ipython/ipython/issues/11038
-        try:
-            dct = self.to_dict()
-        except Exception:
-            utils.display_traceback(in_ipython=True)
-            return {}
-        else:
-            return renderers.get()(dct)
-
     def repeat(self, row=Undefined, column=Undefined, **kwargs):
         """Return a RepeatChart built from the chart
 
@@ -854,6 +840,64 @@ def resolve_legend(self, *args, **kwargs):
     def resolve_scale(self, *args, **kwargs):
         return self._set_resolve(scale=core.ScaleResolveMap(*args, **kwargs))
 
+    # Display-related methods
+
+    def _repr_mimebundle_(self, include, exclude):
+        """Return a MIME bundle for display in Jupyter frontends."""
+        # Catch errors explicitly to get around issues in Jupyter frontend
+        # see https://github.com/ipython/ipython/issues/11038
+        try:
+            dct = self.to_dict()
+        except Exception:
+            utils.display_traceback(in_ipython=True)
+            return {}
+        else:
+            return renderers.get()(dct)
+
+    def display(self):
+        """Display chart in Jupyter notebook or JupyterLab"""
+        from IPython.display import display
+        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,
+              **kwargs):
+        """Open a browser window and display a rendering of the chart
+
+        Parameters
+        ----------
+        html : string
+            HTML to serve
+        ip : string (default = '127.0.0.1')
+            ip address at which the HTML will be served.
+        port : int (default = 8888)
+            the port at which to serve the HTML
+        n_retries : int (default = 50)
+            the number of nearby ports to search if the specified port
+            is already in use.
+        files : dictionary (optional)
+            dictionary of extra content to serve
+        jupyter_warning : bool (optional)
+            if True (default), then print a warning if this is used
+            within the Jupyter notebook
+        open_browser : bool (optional)
+            if True (default), then open a web browser to the given HTML
+        http_server : class (optional)
+            optionally specify an HTTPServer class to use for showing the
+            figure. The default is Python's basic HTTPServer.
+        **kwargs :
+            additional keyword arguments passed to the save() method
+        """
+        from ...utils.server import serve
+
+        html = six.StringIO()
+        self.save(html, format='html', **kwargs)
+        html.seek(0)
+
+        serve(html.read(), ip=ip, port=port, n_retries=n_retries,
+              files=files, jupyter_warning=jupyter_warning,
+              open_browser=open_browser, http_server=http_server)
+
 
 class EncodingMixin(object):
     @utils.use_signature(core.EncodingWithFacet)
diff --git a/altair/vegalite/v2/examples/simple_scatter.py b/altair/vegalite/v2/examples/simple_scatter.py
--- a/altair/vegalite/v2/examples/simple_scatter.py
+++ b/altair/vegalite/v2/examples/simple_scatter.py
@@ -1,9 +1,10 @@
 """
-Simple Scatter Plot
--------------------
+Simple Scatter Plot with Tooltips
+---------------------------------
 
 A simple example of an interactive scatter plot using the well-known iris
-dataset.
+dataset, with tooltips that show the species name when the mouse hovers
+over each point.
 """
 # category: simple charts
 
@@ -15,5 +16,6 @@
 alt.Chart(iris).mark_point().encode(
     x='petalWidth',
     y='petalLength',
-    color='species'
+    color='species',
+    tooltip='species'
 ).interactive()


Test patch: