Dynamically embed precomputed plot with Javascript

In my standalone website, a button click by the user updates the text of a bokeh.models.Div via Javascript. Usually, this text only includes, well text, images and some formatting instructions. I wonder whether I’d also be able to include a precomputed bokeh.plotting.figure somewhere in the text in this way as well.

A minimal example is below. My hope was to do this with a json_item embedding. Essentially, precompute the figure in Python, obtain the JSON with json_item(), save it somewhere later accessible from Javascript. Then in Javascript set the Div’s text field to a div tag with the chosen #id and call Bokeh.embed.embed_item() on the JSON information. This pretty much works except for the fact that #id is not found, because it is contained in a shadowroot. How could I solve this?

Example:

from bokeh import events
from bokeh.embed import json_item
from bokeh.layouts import layout
from bokeh.models import Button, CustomJS
from bokeh.models import Div
from bokeh.plotting import figure, show
import json

p = figure()
p.line(x=[0, 1], y=[0, 1])
div = Div(text='', name='mydiv')
button = Button(label='Click')

item_text = json.dumps(json_item(p, "myplot"))

button.js_on_event(events.ButtonClick, CustomJS(code=f"""
    let div = Bokeh.documents[0].get_model_by_name('mydiv');
    div.text = '<div id="myplot"></div>';
    let item = JSON.parse(data);    
    
    // Raises error "Error rendering Bokeh model: could not find #myplot HTML tag"
    Bokeh.embed.embed_item(item);
""", args=dict(data=item_text)))

show(layout([button, div]))

Edit: I now realized that I can pass a html-DOM-reference to embed_item, as in Bokeh.embed.embed_item(item, dom). Still, it would be nice to realize this within the Bokeh components without having to figure out the dom-tree to a component, which can be quite brittle and complicated.

Preliminary solution:

from bokeh import events
from bokeh.embed import json_item
from bokeh.layouts import layout
from bokeh.models import Button, CustomJS
from bokeh.models import Div
from bokeh.plotting import figure, show
import json

p = figure()
p.line(x=[0, 1], y=[0, 1])
div = Div(text='', name='mydiv')
button = Button(label='Click')

item_text = json.dumps(json_item(p, "myplot"))

button.js_on_event(events.ButtonClick, CustomJS(code=f"""
    let div = Bokeh.documents[0].get_model_by_name('mydiv');
    let json = JSON.parse(data);
    div.text = `<p>Some text</p><div id="${json.target_id}"></div>`;
    let domref = document.querySelector(`div .bk-Column`).shadowRoot.
      querySelector('div.bk-Div').shadowRoot.querySelector(`div #${json.target_id}`);
    Bokeh.embed.embed_item(json, domref);
""", args=dict(data=item_text)))

show(layout([button, div]))

This is my #1 struggle with Bokeh, that quite often I find myself in a situation in which I need the actual reference to a DOM element. I wonder whether it would be possible for Bokeh to save the DOM element reference for each component during the computation of the layout?

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.