Using MyST-NB-Bokeh#

MyST-NB-Bokeh provides one function that glues a Bokeh figure into the Notebook and provides hooks for MyST-NB to read the glued data and paste it into a Markdown cell.

Adding to Configuration#

To use this extension, you will need to install it into your environment and then modify your Sphinx configuration. How you do this depends on what interface to Sphinx you’re using.

Standard Sphinx Site#

If you are building a standard Sphinx site, then the myst_nb_bokeh extension should be added to your conf.py file:

extensions = [
    ...
    "myst_nb_bokeh",
    ...
]

Note that the three dots indicate other values that may be in your extensions list, you should not copy them.

JupyterBook#

If you’re building a JupyterBook, then you’ll need to modify your Sphinx configuration in your _config.yml file. In this case, you need to add a custom Sphinx extension:

sphinx:
  extra_extensions:
  - myst_nb_bokeh

Configuring MyST-NB-Bokeh#

MyST-NB-Bokeh has no configuration options 🎉

When the extension is added to your Sphinx configuration, it will set one MyST-NB-bokeh specific configuration values:

  1. nb_mime_priority_overrides: This configuration value determines the priority of the mimetypes in the cell output. This extension sets our custom JS+Bokeh MIME type to be the highest priority for HTML rendering.

Gluing and Pasting Bokeh Figures#

To glue and paste Bokeh figures in your Notebooks, you need to import one function from this package: glue_bokeh(). You do not need to run the show() or any other output_* (for example, output_notebook()) functions to be able to glue and paste Bokeh figures. The BokehJS library is automatically added to the page if necessary, whenever a Bokeh figure is pasted.

glue_bokeh() takes two mandatory and one optional argument, so it has the same arguments as the glue() function from MyST-NB:

  1. name: Mandatory, the name under which the Bokeh figure should be stored.

  2. variable: Mandatory, the Bokeh figure to glue into the output.

  3. display: Optional, default False. When True, the plot will be displayed in the output of the cell where gluing takes place. This is convenient to inspect the plot.

Once a Bokeh figure has been glued into the Notebook, you can use all the same pasting directive functionality as is available from MyST-NB. Note that you cannot paste Bokeh plots inline to the text, because of how the plot is displayed.

The example below shows how to glue and paste Bokeh figures.

from bokeh.plotting import figure

from myst_nb_bokeh import glue_bokeh
p = figure(width=300, height=300)
p.circle(list(range(1, 10)), list(range(10, 1, -1)));
# When display is False or omitted, the figure is not displayed in this cell's output
glue_bokeh("bokeh_plot", p)
---------------------------------------------------------------------------
UnsetValueError                           Traceback (most recent call last)
Cell In[3], line 2
      1 # When display is False or omitted, the figure is not displayed in this cell's output
----> 2 glue_bokeh("bokeh_plot", p)

File ~/checkouts/readthedocs.org/user_builds/myst-nb-bokeh/envs/latest/lib/python3.11/site-packages/myst_nb_bokeh/__init__.py:87, in glue_bokeh(name, variable, display)
     84 mime_prefix = "" if display else MYST_NB_GLUE_PREFIX
     85 metadata = {"scrapbook": dict(name=name, mime_prefix=mime_prefix, has_bokeh=True)}
     86 ipy_display(
---> 87     {mime_prefix + JB_BOKEH_MIMETYPE: json.dumps(json_item(variable), separators=(",", ":"))},
     88     raw=True,
     89     metadata=metadata,
     90 )
     91 if display:
     92     script, div = components(variable, wrap_script=False)

File ~/checkouts/readthedocs.org/user_builds/myst-nb-bokeh/envs/latest/lib/python3.11/site-packages/bokeh/embed/standalone.py:428, in json_item(model, target, theme)
    370 def json_item(model: Model, target: ID | None = None, theme: ThemeLike = None) -> StandaloneEmbedJson:
    371     ''' Return a JSON block that can be used to embed standalone Bokeh content.
    372 
    373     Args:
   (...)    426 
    427     '''
--> 428     with OutputDocumentFor([model], apply_theme=theme) as doc:
    429         doc.title = ""
    430         [doc_json] = standalone_docs_json([model]).values()

File ~/.asdf/installs/python/3.11.14/lib/python3.11/contextlib.py:137, in _GeneratorContextManager.__enter__(self)
    135 del self.args, self.kwds, self.func
    136 try:
--> 137     return next(self.gen)
    138 except StopIteration:
    139     raise RuntimeError("generator didn't yield") from None

File ~/checkouts/readthedocs.org/user_builds/myst-nb-bokeh/envs/latest/lib/python3.11/site-packages/bokeh/embed/util.py:153, in OutputDocumentFor(objs, apply_theme, always_new)
    151     doc = _new_doc()
    152     for model in objs:
--> 153         doc.add_root(model)
    155 # handle a single shared document
    156 elif len(docs) == 1:

File ~/checkouts/readthedocs.org/user_builds/myst-nb-bokeh/envs/latest/lib/python3.11/site-packages/bokeh/document/document.py:338, in Document.add_root(self, model, setter)
    335 if model in self._roots:
    336     return
--> 338 with self.models.freeze():
    339     self._roots.append(model)
    341 self.callbacks.trigger_on_change(RootAddedEvent(self, model, setter))

File ~/.asdf/installs/python/3.11.14/lib/python3.11/contextlib.py:144, in _GeneratorContextManager.__exit__(self, typ, value, traceback)
    142 if typ is None:
    143     try:
--> 144         next(self.gen)
    145     except StopIteration:
    146         return False

File ~/checkouts/readthedocs.org/user_builds/myst-nb-bokeh/envs/latest/lib/python3.11/site-packages/bokeh/document/models.py:135, in DocumentModelManager.freeze(self)
    133 self._push_freeze()
    134 yield
--> 135 self._pop_freeze()

File ~/checkouts/readthedocs.org/user_builds/myst-nb-bokeh/envs/latest/lib/python3.11/site-packages/bokeh/document/models.py:288, in DocumentModelManager._pop_freeze(self)
    286 self._freeze_count -= 1
    287 if self._freeze_count == 0:
--> 288     self.recompute()

File ~/checkouts/readthedocs.org/user_builds/myst-nb-bokeh/envs/latest/lib/python3.11/site-packages/bokeh/document/models.py:215, in DocumentModelManager.recompute(self)
    213 new_models: set[Model] = set()
    214 for mr in document.roots:
--> 215     new_models |= mr.references()
    217 old_models = set(self._models.values())
    219 to_detach = old_models - new_models

File ~/checkouts/readthedocs.org/user_builds/myst-nb-bokeh/envs/latest/lib/python3.11/site-packages/bokeh/model/model.py:496, in Model.references(self)
    492 def references(self) -> set[Model]:
    493     ''' Returns all ``Models`` that this object has references to.
    494 
    495     '''
--> 496     return set(collect_models(self))

File ~/checkouts/readthedocs.org/user_builds/myst-nb-bokeh/envs/latest/lib/python3.11/site-packages/bokeh/model/util.py:139, in collect_models(*input_values)
    123 def collect_models(*input_values: Any) -> list[Model]:
    124     ''' Collect a duplicate-free list of all other Bokeh models referred to by
    125     this model, or by any of its references, etc.
    126 
   (...)    137 
    138     '''
--> 139     return collect_filtered_models(None, *input_values)

File ~/checkouts/readthedocs.org/user_builds/myst-nb-bokeh/envs/latest/lib/python3.11/site-packages/bokeh/model/util.py:119, in collect_filtered_models(discard, *input_values)
    117         ids.add(obj.id)
    118         collected.append(obj)
--> 119         visit_immediate_value_references(obj, queue_one)
    121 return collected

File ~/checkouts/readthedocs.org/user_builds/myst-nb-bokeh/envs/latest/lib/python3.11/site-packages/bokeh/model/util.py:185, in visit_immediate_value_references(value, visitor)
    183 if isinstance(value, HasProps):
    184     for attr in value.properties_with_refs():
--> 185         child = getattr(value, attr)
    186         visit_value_and_its_immediate_references(child, visitor)
    187 else:

File ~/checkouts/readthedocs.org/user_builds/myst-nb-bokeh/envs/latest/lib/python3.11/site-packages/bokeh/core/property/descriptors.py:282, in PropertyDescriptor.__get__(self, obj, owner)
    280     value = self._get(obj)
    281     if value is Undefined:
--> 282         raise UnsetValueError(f"{obj}.{self.name} doesn't have a value set")
    283     return value
    285 elif owner is not None:

UnsetValueError: Circle(id='p1043', ...).radius doesn't have a value set

Once the figure is glued into the output, you can paste it using the {glue:}, {glue:any}, and {glue:figure} directives. MyST has the triple-backtick and colon-fence ways of using directives; we’re using the colon-fence method in this document. The following Markdown will paste the bokeh_plot that we glued in the last cell:

We can also use the {glue:figure} directive to add a caption and cross-referencing. You can paste the same figure multiple times. Each version of the figure is completely independent of each other.