Repository: bokeh/bokeh

Issue:
Only first plot in jupyter notebook cell is shown
*(Note that I have a proposed fix for this Issue. I'll open a PR.)*

#### Software version info
*Bokeh:* 0.12.10
*Python:* 3.6.1 (default, Apr  4 2017, 09:40:21) 
    [GCC 4.2.1 Compatible Apple LLVM 8.1.0 (clang-802.0.38)]
*Notebook:* Jupyter notebook (5.2.0)
*OS:* MacOS 10.12.6
*Browser:* Google chrome version 61.0.3163.100 (Official Build) (64-bit)

#### Expected behavior and the observed behavior

Expected:
When multiple bokeh plots are defined and shown in a notebook code cell, I expect all plots to appear in the cell's output.

Observed:
Only the first plot is output. The subsequent plots are absent.

#### Complete, minimal, self-contained example code that reproduces the issue

```
import bokeh.plotting
import bokeh.io

bokeh.io.output_notebook()

print("Plot 1:")
p1 = bokeh.plotting.figure(plot_height=200)
p1.line([1,2,3],[1,2,3])
bokeh.plotting.show(p1)

print("Plot 2:")
p2 = bokeh.plotting.figure(plot_height=200)
p2.line([1,2,3],[1,4,9])
bokeh.plotting.show(p2)
```
(Note that, the resulting .ipynb file contains all the necessary plot data and there are no JavaScript errors in the browser console.)

#### Screenshot
![image](https://user-images.githubusercontent.com/212219/32147627-c54d089c-bcc0-11e7-89e2-8e2bb903ac60.png)



Discussion:

Resolving patch:
diff --git a/bokeh/io/output.py b/bokeh/io/output.py
--- a/bokeh/io/output.py
+++ b/bokeh/io/output.py
@@ -83,7 +83,9 @@ def output_file(filename, title="Bokeh Plot", mode="cdn", root_dir=None):
 @general((1,0,0))
 def output_notebook(resources=None, verbose=False, hide_banner=False, load_timeout=5000, notebook_type='jupyter'):
     ''' Configure the default output state to generate output in notebook cells
-    when :func:`show` is called.
+    when :func:`show` is called. Note that, :func:`show` may be called multiple
+    times in a single cell to display multiple objects in the output cell. The
+    objects will be displayed in order.
 
     Args:
         resources (Resource, optional) :
diff --git a/bokeh/io/showing.py b/bokeh/io/showing.py
--- a/bokeh/io/showing.py
+++ b/bokeh/io/showing.py
@@ -41,6 +41,9 @@
 def show(obj, browser=None, new="tab", notebook_handle=False, notebook_url="localhost:8888"):
     ''' Immediately display a Bokeh object or application.
 
+        :func:`show` may be called multiple times in a single Jupyter notebook
+        cell to display multiple objects. The objects are displayed in order.
+
     Args:
         obj (LayoutDOM or Application) :
             A Bokeh object to display.


Test patch:
diff --git a/bokeh/core/tests/test_templates.py b/bokeh/core/tests/test_templates.py
new file mode 100644
--- /dev/null
+++ b/bokeh/core/tests/test_templates.py
@@ -0,0 +1,204 @@
+from os.path import abspath, join, split, pardir
+
+
+TOP_PATH = abspath(join(split(__file__)[0], pardir))
+
+pinned_template = """{% extends "autoload_js.js" %}
+
+{% block register_mimetype %}
+
+  {%- if register_mime -%}
+  var JS_MIME_TYPE = 'application/javascript';
+  var HTML_MIME_TYPE = 'text/html';
+  var EXEC_MIME_TYPE = 'application/vnd.bokehjs_exec.v0+json';
+  var CLASS_NAME = 'output_bokeh rendered_html';
+
+  /**
+   * Render data to the DOM node
+   */
+  function render(props, node) {
+    var script = document.createElement("script");
+    node.appendChild(script);
+  }
+
+  /**
+   * Handle when an output is cleared or removed
+   */
+  function handleClearOutput(event, handle) {
+    var cell = handle.cell;
+
+    var id = cell.output_area._bokeh_element_id;
+    var server_id = cell.output_area._bokeh_server_id;
+    // Clean up Bokeh references
+    if (id !== undefined) {
+      Bokeh.index[id].model.document.clear();
+      delete Bokeh.index[id];
+    }
+
+    if (server_id !== undefined) {
+      // Clean up Bokeh references
+      var cmd = "from bokeh.io.state import curstate; print(curstate().uuid_to_server['" + server_id + "'].get_sessions()[0].document.roots[0]._id)";
+      cell.notebook.kernel.execute(cmd, {
+        iopub: {
+          output: function(msg) {
+            var element_id = msg.content.text.trim();
+            Bokeh.index[element_id].model.document.clear();
+            delete Bokeh.index[element_id];
+          }
+        }
+      });
+      // Destroy server and session
+      var cmd = "import bokeh.io.notebook as ion; ion.destroy_server('" + server_id + "')";
+      cell.notebook.kernel.execute(cmd);
+    }
+  }
+
+  /**
+   * Handle when a new output is added
+   */
+  function handleAddOutput(event, handle) {
+    var output_area = handle.output_area;
+    var output = handle.output;
+
+    // limit handleAddOutput to display_data with EXEC_MIME_TYPE content only
+    if ((output.output_type != "display_data") || (!output.data.hasOwnProperty(EXEC_MIME_TYPE))) {
+      return
+    }
+
+    var toinsert = output_area.element.find("." + CLASS_NAME.split(' ')[0]);
+
+    if (output.metadata[EXEC_MIME_TYPE]["id"] !== undefined) {
+      toinsert[toinsert.length - 1].firstChild.textContent = output.data[JS_MIME_TYPE];
+      // store reference to embed id on output_area
+      output_area._bokeh_element_id = output.metadata[EXEC_MIME_TYPE]["id"];
+    }
+    if (output.metadata[EXEC_MIME_TYPE]["server_id"] !== undefined) {
+      var bk_div = document.createElement("div");
+      bk_div.innerHTML = output.data[HTML_MIME_TYPE];
+      var script_attrs = bk_div.children[0].attributes;
+      for (var i = 0; i < script_attrs.length; i++) {
+        toinsert[toinsert.length - 1].firstChild.setAttribute(script_attrs[i].name, script_attrs[i].value);
+      }
+      // store reference to server id on output_area
+      output_area._bokeh_server_id = output.metadata[EXEC_MIME_TYPE]["server_id"];
+    }
+  }
+
+  function register_renderer(events, OutputArea) {
+
+    function append_mime(data, metadata, element) {
+      // create a DOM node to render to
+      var toinsert = this.create_output_subarea(
+        metadata,
+        CLASS_NAME,
+        EXEC_MIME_TYPE
+      );
+      this.keyboard_manager.register_events(toinsert);
+      // Render to node
+      var props = {data: data, metadata: metadata[EXEC_MIME_TYPE]};
+      render(props, toinsert[toinsert.length - 1]);
+      element.append(toinsert);
+      return toinsert
+    }
+
+    /* Handle when an output is cleared or removed */
+    events.on('clear_output.CodeCell', handleClearOutput);
+    events.on('delete.Cell', handleClearOutput);
+
+    /* Handle when a new output is added */
+    events.on('output_added.OutputArea', handleAddOutput);
+
+    /**
+     * Register the mime type and append_mime function with output_area
+     */
+    OutputArea.prototype.register_mime_type(EXEC_MIME_TYPE, append_mime, {
+      /* Is output safe? */
+      safe: true,
+      /* Index of renderer in `output_area.display_order` */
+      index: 0
+    });
+  }
+
+  // register the mime type if in Jupyter Notebook environment and previously unregistered
+  if (root.Jupyter !== undefined) {
+    var events = require('base/js/events');
+    var OutputArea = require('notebook/js/outputarea').OutputArea;
+
+    if (OutputArea.prototype.mime_types().indexOf(EXEC_MIME_TYPE) == -1) {
+      register_renderer(events, OutputArea);
+    }
+  }
+  {%- endif -%}
+
+{% endblock %}
+
+{% block autoload_init %}
+  if (typeof (root._bokeh_timeout) === "undefined" || force === true) {
+    root._bokeh_timeout = Date.now() + {{ timeout|default(0)|json }};
+    root._bokeh_failed_load = false;
+  }
+
+  var NB_LOAD_WARNING = {'data': {'text/html':
+     "<div style='background-color: #fdd'>\\n"+
+     "<p>\\n"+
+     "BokehJS does not appear to have successfully loaded. If loading BokehJS from CDN, this \\n"+
+     "may be due to a slow or bad network connection. Possible fixes:\\n"+
+     "</p>\\n"+
+     "<ul>\\n"+
+     "<li>re-rerun `output_notebook()` to attempt to load from CDN again, or</li>\\n"+
+     "<li>use INLINE resources instead, as so:</li>\\n"+
+     "</ul>\\n"+
+     "<code>\\n"+
+     "from bokeh.resources import INLINE\\n"+
+     "output_notebook(resources=INLINE)\\n"+
+     "</code>\\n"+
+     "</div>"}};
+
+  function display_loaded() {
+    var el = document.getElementById({{ elementid|json }});
+    if (el != null) {
+      el.textContent = "BokehJS is loading...";
+    }
+    if (root.Bokeh !== undefined) {
+      if (el != null) {
+        el.textContent = "BokehJS " + root.Bokeh.version + " successfully loaded.";
+      }
+    } else if (Date.now() < root._bokeh_timeout) {
+      setTimeout(display_loaded, 100)
+    }
+  }
+{% endblock %}
+
+{% block run_inline_js %}
+    if ((root.Bokeh !== undefined) || (force === true)) {
+      for (var i = 0; i < inline_js.length; i++) {
+        inline_js[i].call(root, root.Bokeh);
+      }
+      {%- if elementid -%}
+      if (force === true) {
+        display_loaded();
+      }
+      {%- endif -%}
+    } else if (Date.now() < root._bokeh_timeout) {
+      setTimeout(run_inline_js, 100);
+    } else if (!root._bokeh_failed_load) {
+      console.log("Bokeh: BokehJS failed to load within specified timeout.");
+      root._bokeh_failed_load = true;
+    } else if (force !== true) {
+      var cell = $(document.getElementById({{ elementid|json }})).parents('.cell').data().cell;
+      cell.output_area.append_execute_result(NB_LOAD_WARNING)
+    }
+{% endblock %}
+"""  # noqa
+def test_autoload_template_has_changed():
+    """This is not really a test but a reminder that if you change the
+    autoload_nb_js.js template then you should make sure that insertion of
+    plots into notebooks is working as expected. In particular, this test was
+    created as part of https://github.com/bokeh/bokeh/issues/7125.
+    """
+    with open(join(TOP_PATH, '_templates/autoload_nb_js.js'), mode='r') as f:
+        assert pinned_template == f.read(), \
+        """It seems that the template autoload_nb_js.js has changed.
+        If this is voluntary and that proper testing of plots insertion
+        in notebooks has been completed successfully, update this test
+        with the new file contents"""