Repository: bokeh/bokeh

Issue:
Add a type check to `show` for better error reporting
Edit: was: `RuntimeError: Unable to save object of type '<class 'bokeh.models.renderers.GlyphRenderer'>'`

I'm trying to use Bokeh to do a simple function plot. I'm using the IPython notebook. 

```
xvals = np.linspace(-10, 10, 1000)
source = bokeh.models.ColumnDataSource(data=dict(x=xvals, y=np.sin(xvals)))

plot = bokeh.plotting.figure()
line = plot.line(source=source)
bokeh.charts.show(line)
```

This gives

```
---------------------------------------------------------------------------
RuntimeError                              Traceback (most recent call last)
<ipython-input-4-1e1d5182cf85> in <module>()
      1 plot = bokeh.plotting.figure()
      2 line = plot.line(source=source)
----> 3 bokeh.charts.show(line)

/Users/aaronmeurer/anaconda3/lib/python3.5/site-packages/bokeh/io.py in show(obj, browser, new)
    302     if obj not in _state.document.roots:
    303         _state.document.add_root(obj)
--> 304     return _show_with_state(obj, _state, browser, new)
    305 
    306 

/Users/aaronmeurer/anaconda3/lib/python3.5/site-packages/bokeh/io.py in _show_with_state(obj, state, browser, new)
    320 
    321     if state.file or not shown:
--> 322         _show_file_with_state(obj, state, new, controller)
    323 
    324     return comms_handle

/Users/aaronmeurer/anaconda3/lib/python3.5/site-packages/bokeh/io.py in _show_file_with_state(obj, state, new, controller)
    325 
    326 def _show_file_with_state(obj, state, new, controller):
--> 327     filename = save(obj, state=state)
    328     controller.open("file://" + filename, new=_new_param[new])
    329 

/Users/aaronmeurer/anaconda3/lib/python3.5/site-packages/bokeh/io.py in save(obj, filename, resources, title, state, validate)
    398 
    399     filename, resources, title = _get_save_args(state, filename, resources, title)
--> 400     _save_helper(obj, filename, resources, title, validate)
    401     return os.path.abspath(filename)
    402 

/Users/aaronmeurer/anaconda3/lib/python3.5/site-packages/bokeh/io.py in _save_helper(obj, filename, resources, title, validate)
    458             doc = obj
    459         else:
--> 460             raise RuntimeError("Unable to save object of type '%s'" % type(obj))
    461 
    462         if validate:

RuntimeError: Unable to save object of type '<class 'bokeh.models.renderers.GlyphRenderer'>'

In [ ]:
```

If I first run `bokeh.io.output_notebook()` I get

```

Javascript error adding output!
TypeError: Cannot read property 'frame' of undefined
See your browser Javascript console for more details.
```

The console says

```
cdn.pydata.org/bokeh/release/bokeh-0.12.1.min.js:67 Bokeh: JS/Python version mismatch
cdn.pydata.org/bokeh/release/bokeh-0.12.1.min.js:67 Bokeh: Library versions: JS (0.12.0)  /  Python (0.12.1)
main.min.js:20239 TypeError: Cannot read property 'frame' of undefined(…)
```



Discussion:
you need to call show on `plot`. Also tell `line` which columns to use:

<img width="842" alt="screen shot 2016-08-03 at 5 29 36 pm" src="https://cloud.githubusercontent.com/assets/1078448/17384195/e83818cc-599f-11e6-8e78-c90a78f411ff.png">

I guess we need to put a type check in `show` to give better output. 

This mismatch is a herring, there was a glitch with our last release build in updating the js version. 

Also you do have to run `output_notebook` first, and almost definitely in a separate cell, all its own.

The `TypeError: Cannot read property 'frame' of undefined` has to do with the fact that you are showing something that is not show-able (a glyph renderer, instead of the plot).

Ahhh. The `'x'` and `'y'` arguments are also needed I guess (which makes sense since the data is a dictionary). By the way, any reason Bokeh doesn't automatically register Figure so that it just works if you type "plot" at the end of the cell in the notebook?

Also, I really want a `plot` function, akin to `matplotlib.pyplot.plot` that just does the right thing for function plotting. There's too much typing here just to plot a simple x vs. f(x). 

My goal here is to see if bokeh can be used as a backend for SymPy's `plot`. It looks promising so far. The next step is to see how hard it is to make it work with interactivity (so that it computes the data points if you drag the plot to the left or right). 

Unless you need to control things like linked selections, there's no need to create your own `ColumnDataSource` explicitly. You can just:

```
from bokeh.plotting import show, figure, output_notebook

output_notebook() # should go in its own cell

p = figure()
p.line([1,2,3], [4,5,6])

show(p)
```

There was a commented out `_repr_html` in the past, but it was very flaky. Time has to be allowed for BokehJS to load from CDN (or else use `INLINE` resources, but many people asked for `CDN` to be the default), if you do everything "too quickly" then things will fail. I've found no reliable way around this. 

A way to do the last thing you mentioned would be to add CustomJS callbacks on range changes that call the jupyter kernel execute JS API. This is what the DataShader `InteractiveImage` does.

There are also lower level ways to load BokehJS in the notebook that might be appropriate if you are "boxing" Bokeh in another library. 

You don't include the Javascript with the conda package? 

We do, the overwhelming preference was to switch to using `CDN` loading by default. 

I'm not too worried about the API stuff for SymPy, since we would just be wrapping that. 

For the Javascript, though, what would be the cleanest way for us to handle it? It doesn't seem like calling `output_notebook()` automatically when the user does `plot` will work. We could also leave it to the user to do themselves (which we already require for matplotlib; the user has to type `%matplotlib inline` to get inline plots in the notebook). 

If you want to do it for the user, and have it be reliable, use `INLINE` resources. But be aware that makes the saved notebooks considerably larger. 

Otherwise, it seems fine to have the user call it (or some function you make that wraps it) on their own. That's what Holoviews does. It might be helpful to ask @jbednar, et al for their input and experience using Bokeh as a backend for Holoviews. 

Oh, I see why people wanted CDN to be the default :)

I am wondering if we should just use datashader. I saw @jbednar's excellent talk at SciPy, but it wasn't clear to me if it datashader requires computing all points beforehand, or if it also supports accessing the necessary points "on the fly". 

Also, if you want to move discussion this over to https://github.com/sympy/sympy/issues/11469 that's fine. 

Quite possibly. DataShader computes on the fly. I'm not sure if that means it would be easier to use directly, or if would be easier to adapt some of it's machinery (e.g. `InteractiveImage`) for your own use. That's definitely a @jbednar question, though. 

Datashader is currently stateless, with no caching, indexing, or other computation done before the points are aggregated.  So I'm not sure what your needs for accessing things "on the fly" are, but I don't think there's any issue there.  Whether it's appropriate for sympy depends on the balance between the level of interactivity you are hoping to provide, versus the size of your datasets; see the datashader [FAQ](http://datashader.readthedocs.io/en/latest/#faq).  My usual recommendation would be to start with Bokeh, to get maximum interactivity, and move to datashader if you realize your datasets are too big and you're willing to give up some of the interactivity to be able to remove nearly all limitations on dataset size.

InteractiveImage has been very tricky to keep working well with different Bokeh versions, so I wouldn't recommend copying what it does internally in general; instead I'd hope we can move what it does into Bokeh itself so that it will work reliably across Bokeh versions.


Resolving patch:
diff --git a/bokeh/io/showing.py b/bokeh/io/showing.py
--- a/bokeh/io/showing.py
+++ b/bokeh/io/showing.py
@@ -28,6 +28,7 @@
 # External imports
 
 # Bokeh imports
+from ..models.layouts import LayoutDOM
 from ..util.browser import get_browser_controller, NEW_PARAM
 from .notebook import run_notebook_hook
 from .saving import save
@@ -45,7 +46,7 @@ def show(obj, browser=None, new="tab", notebook_handle=False, notebook_url="loca
         cell to display multiple objects. The objects are displayed in order.
 
     Args:
-        obj (LayoutDOM or Application) :
+        obj (LayoutDOM or Application or callable) :
             A Bokeh object to display.
 
             Bokeh plots, widgets, layouts (i.e. rows and columns) may be
@@ -127,9 +128,16 @@ def show(obj, browser=None, new="tab", notebook_handle=False, notebook_url="loca
     '''
     state = curstate()
 
+    is_application = getattr(obj, '_is_a_bokeh_application_class', False)
+
+    if not (isinstance(obj, LayoutDOM) or is_application or callable(obj)):
+        raise ValueError(_BAD_SHOW_MSG)
+
+    # TODO (bev) check callable signature more thoroughly
+
     # This ugliness is to prevent importing bokeh.application (which would bring
     # in Tornado) just in order to show a non-server object
-    if getattr(obj, '_is_a_bokeh_application_class', False) or callable(obj):
+    if is_application or callable(obj):
         return run_notebook_hook(state.notebook_type, 'app', obj, state, notebook_url)
 
     if obj not in state.document.roots:
@@ -144,6 +152,13 @@ def show(obj, browser=None, new="tab", notebook_handle=False, notebook_url="loca
 # Private API
 #-----------------------------------------------------------------------------
 
+_BAD_SHOW_MSG = """"Invalid object to show. The object to passed to show must be one of:
+
+* a LayoutDOM (e.g. a Plot or Widget or Layout)
+* a Bokeh Application
+* a callable suitable to an application FunctionHandler
+"""
+
 def _show_file_with_state(obj, state, new, controller):
     '''
 


Test patch:
diff --git a/bokeh/io/tests/test_showing.py b/bokeh/io/tests/test_showing.py
--- a/bokeh/io/tests/test_showing.py
+++ b/bokeh/io/tests/test_showing.py
@@ -31,6 +31,7 @@
 from bokeh.io.output import output_notebook
 from bokeh.io.state import curstate, State
 from bokeh.models.plots import Plot
+from bokeh.models.renderers import GlyphRenderer
 
 # Module under test
 import bokeh.io.showing as bis
@@ -113,6 +114,10 @@ def test_show_doesnt_duplicate_if_already_there(m):
     bis.show(p)
     assert curstate().document.roots == [p]
 
+@pytest.mark.parametrize('obj', [1, 2.3, None, "str", GlyphRenderer()])
+def test_show_with_bad_object(obj):
+    with pytest.raises(ValueError):
+        bis.show(obj)
 
 #-----------------------------------------------------------------------------
 # Dev API