Embedding interactive bokeh content in flask

Hi folks,

I have bokeh application with around 6 tabs and 3-4 interactive plots under each tab. These plots have a lot of datapoints and take time to plot. Loading all plots in one shot is fairly slow, so I want to use flask to draw one tab at a time:

index.html

<html>
<head>
   
    <script type="text/javascript">
        function openvtab(evt, tabname) {
            fetch('/tabs/' + tabname)
                .then(response => response.text())
                .then(html => {
                    console.log(document.getElementById('tabitems'));
                    document.getElementById('tabitems').innerHTML = html
                    })
        }
    </script>
</head>
<body>
    <div class="mytab" style="width:135px">
      <button class="button-active tablink" onclick="openvtab(event, 'a1')">A1</button>
      <button class="button-inactive tablink" onclick="openvtab(event, 'b1')">B1</button>
    </div>
    <div style="margin-left:140px">
      <div id="tabitems" class="vani-tab" style="display:block"></div>
    </div>
</body>
</html>

app/routes.py

app_html = """
    {{bokeh_script|safe}}
"""

bokeh_process = subprocess.Popen(
    ['python', '-m', 'bokeh', 'serve', '--address','10.40.14.5', '--port', '5002', '--allow-websocket-origin=10.40.14.5:5000', 'dashboard'], stdout=subprocess.PIPE)

@atexit.register
def kill_bokeh_server():
    bokeh_process.kill()

@app.route('/')
def index():
    return render_template('index.html', resource=CDN.render().strip())

@app.route('/tabs/<string:tabname>')
def rendertab(tabname):
    app.logger.info('in '+tabname)
    bokeh_script = server_document(url='http://10.40.14.5:5002/dashboard', arguments=dict(tabname=tabname))
    return render_template_string(app_html, bokeh_script = bokeh_script)

Now looking at the returned html on the browser, I see that the script is downloaded and expanded for each of the tabs, but is not executed (so doesn’t expand to actual chart). This is expected because innerHTML writes it as a string. However appendChild itself fails because the return of render_template_string is a string and not a JS object.

What’s the best way to handle this?

I think your only option is to eval the script body in openvtab. So you will need to strip off the outer HTML markup (or else re-implement the parts of server_session that create the script source code).

Tried that but I get this error on loading it on the browser:

bokeh-2.2.3.min.js:573 Uncaught (in promise) Error: Error rendering Bokeh model: could not find #1004 HTML tag
    at l (bokeh-2.2.3.min.js:573)
    at Object.n._resolve_element (bokeh-2.2.3.min.js:573)
    at O (bokeh-2.2.3.min.js:164)
    at bokeh-2.2.3.min.js:164
    at bokeh-2.2.3.min.js:185
l @ bokeh-2.2.3.min.js:573
n._resolve_element @ bokeh-2.2.3.min.js:573
O @ bokeh-2.2.3.min.js:164
(anonymous) @ bokeh-2.2.3.min.js:164
(anonymous) @ bokeh-2.2.3.min.js:185
async function (async)
t.embed_items @ bokeh-2.2.3.min.js:164
embed_document @ fcb29e6b-aeda-4632-b3b9-69a59d29d783:118
(anonymous) @ fcb29e6b-aeda-4632-b3b9-69a59d29d783:122
(anonymous) @ fcb29e6b-aeda-4632-b3b9-69a59d29d783:138
o.safely @ bokeh-2.2.3.min.js:583
fn @ fcb29e6b-aeda-4632-b3b9-69a59d29d783:112
(anonymous) @ fcb29e6b-aeda-4632-b3b9-69a59d29d783:141
inline_js @ fcb29e6b-aeda-4632-b3b9-69a59d29d783:143
run_inline_js @ fcb29e6b-aeda-4632-b3b9-69a59d29d783:154
(anonymous) @ fcb29e6b-aeda-4632-b3b9-69a59d29d783:165
(anonymous) @ fcb29e6b-aeda-4632-b3b9-69a59d29d783:27
run_callbacks @ fcb29e6b-aeda-4632-b3b9-69a59d29d783:25
on_load @ fcb29e6b-aeda-4632-b3b9-69a59d29d783:55
load (async)
load_libs @ fcb29e6b-aeda-4632-b3b9-69a59d29d783:80
(anonymous) @ fcb29e6b-aeda-4632-b3b9-69a59d29d783:163
(anonymous) @ fcb29e6b-aeda-4632-b3b9-69a59d29d783:168

This is probably because the plots haven’t been loaded into curdoc yet.

Is there a way to NOT embed all the plots/tabs when loading a session/document?

@codeyman That would definitely cause a problem. This is definitely not intended / standard usage, so I am not too surprised that there are issues. I’m not sure what guidance I can offer here, it seems likely that this would need to be properly developed as a supported use case, which might mean new development is needed. A github issue would be the next step to start that discussion. Just a heads up: what would help move the needle, more than anything, is a fleshed out proposal, i.e. a complete example script of what you’d like to be able to do, that would serve as the benchmark for success (your example script works when the work is complete).

@Bryan,

To rephrase the problem in a different way. Let’s say I’ve already loaded all my plots into curdoc(). Is there a way to just get any added plot later via bokeh server?

p1 = figure(..., name='plot1')
p2 = figure(..., name='plot2')
curdoc().add_root(p1)
curdoc().add_root(p2)

@app.route('/')
def index():
    with pull_session(url="..") as dsession:
       #stash the sessionid here

app.route('/tabs/<string:tabname>')
def renderTab(tabname):
    #Assuming there is some API like this that just gives me the figure for a particular session
    #tabname can be plot1 or plot2 here.
    plotscript = server_session(session_id=session.id, url='...', plotname=tabname)

That I’m actual looking for is something like Bokeh.embed.embed_items that can serve live graphs from the session.

@codeyman The above code snippet above definitely won’t work for a fundamental reason. The app code is only executed once per session, when the session is created. So HTTP arguments e.g plotname for a session that already exists, will never get used (because the app code that might switch on that value has already run, and won’t run again for this session).

Edit: and I had to double check but yes, there is not any parameters to server_session for passing HTTP arguments (if there were, I’d argue that would be a mistake to fix).

So then an off-the-cuff idea might be to afford a way to specify a “session embed” callback that can be run whenever a session is embedded in order to customize the existing session further. That seems like it would satisfy your needs, but it would require new developement, and also discussion (in case there are good reasons not to add that, or other better ways to accomplish things)

Makes sense. I shall raise an enhancement request for this.

1 Like