Hi Luigi,
Sorry about not showing any code example. In the example below I run Flask that embeds a server_session
. I am using the sliders.py
code with some changes. I make use of CustomJS
in order to run JS code from Bokeh. In this example I attached a callback to a TapTool
. The callback will update the text in a non-Bokeh document element when one taps/click the line in the plot.
tap_callback = CustomJS(
args = {
'src': source
},
code='''
// Bokeh JS code
console.log("tap callback");
// if one needs to use the index of the source data for something
// use .selected.indicies if circle/scatter glyph
//const idx = src.selected.indices;
const idx = src.selected.line_indices;
// update a non-Bokeh document element
const elm = document.getElementsByClassName("bk-plot-info")[0];
elm.innerText = "You tapped the line in Bokeh plot";
// ... or have the Bokeh JS in an imported JS function loaded in header
bokeh_func();
'''
)
tap_tool = TapTool()
tap_tool.callback = tap_callback
plot.add_tools(tap_tool)
One enters JS code in the code
argument for CustomJS
as text string. Either the whole code or reference to a JS function that has been loaded from external file defined in header.
I also make use of the name
argument in the definition of the figure
object (name=plot
). This in order to reference and update the Bokeh object from a non-Bokeh JS function. This happens through the JS function updateBokehPlotTitle
which is attached to the non-Bokeh input
form. In this function one gets hold of the Bokeh plot (since we want to update the title) using BokehJS get_model_by_name
:
function updateBokehPlotTitle(form) {
console.log('Update Bokeh title');
const bkplt = window.Bokeh.documents[0].get_model_by_name('plot');
bkplt.title.text = form.title.value;
}
All the code is below where I have it in multiple files. Css styles and JS functions are within static/css
and static/js
respectively.
Bokeh sliders app started with
bokeh serve --allow-websocket-origin=localhost:8000 sliders.py
Hope it helps (and that I have understood your question correctly).
#app.py
from flask import Flask, render_template
from bokeh.embed import server_document, server_session
from bokeh.client import pull_session
BK_URL = 'http://localhost:5006/'
BK_ENDPOINT = 'sliders'
app = Flask(__name__)
session_id = None
@app.route('/')
def index():
url=f'{BK_URL}{BK_ENDPOINT}'
with pull_session(url=url) as session:
bk_script = server_session(session_id=session.id, url=url)
return render_template('index.html', bk_script=bk_script)
if __name__ == '__main__':
app.run(port=8000, debug=True)
# sliders.py
import numpy as np
from bokeh.io import curdoc
from bokeh.layouts import column, row
from bokeh.models import ColumnDataSource, Slider, TextInput
from bokeh.models import TapTool, CustomJS
from bokeh.plotting import figure
# Set up data
N = 200
x = np.linspace(0, 4*np.pi, N)
y = np.sin(x)
source = ColumnDataSource(data=dict(x=x, y=y))
# Set up plot
plot = figure(
height=400,
width=400,
title="my sine wave",
tools="crosshair,pan,reset,save,wheel_zoom",
x_range=[0, 4*np.pi],
y_range=[-2.5, 2.5],
name = 'plot'
)
plot.line('x', 'y', source=source, line_width=4, line_alpha=0.6)
tap_callback = CustomJS(
args = {
'src': source
},
code='''
// Bokeh JS code
console.log("tap callback");
// if one needs to use the index of the source data for something
// use .selected.indicies if circle/scatter glyph
//const idx = src.selected.indices;
const idx = src.selected.line_indices;
// update a non-Bokeh document element
const elm = document.getElementsByClassName("bk-plot-info")[0];
elm.innerText = "You tapped the line in Bokeh plot";
// ... or have the Bokeh JS in an imported JS function loaded in header
bokeh_func();
'''
)
tap_tool = TapTool()
tap_tool.callback = tap_callback
plot.add_tools(tap_tool)
# Set up widgets
text = TextInput(title="title", value='my sine wave', name = 'title_text')
offset = Slider(title="offset", value=0.0, start=-5.0, end=5.0, step=0.1)
amplitude = Slider(title="amplitude", value=1.0, start=-5.0, end=5.0, step=0.1)
phase = Slider(title="phase", value=0.0, start=0.0, end=2*np.pi)
freq = Slider(title="frequency", value=1.0, start=0.1, end=5.1, step=0.1)
# Set up callbacks
def update_title(attrname, old, new):
plot.title.text = text.value
text.on_change('value', update_title)
def update_data(attrname, old, new):
# Get the current slider values
a = amplitude.value
b = offset.value
w = phase.value
k = freq.value
# Generate the new curve
x = np.linspace(0, 4*np.pi, N)
y = a*np.sin(k*x + w) + b
source.data = dict(x=x, y=y)
for w in [offset, amplitude, phase, freq]:
w.on_change('value', update_data)
# Set up layouts and add to document
inputs = column(text, offset, amplitude, phase, freq, name = 'input_wdg')
curdoc().add_root(row(inputs, plot, width=800))
curdoc().title = "Sliders"
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<link href="{{ url_for('static', filename='css/styles.css') }}" rel="stylesheet" type="text/css">
<script type="text/javascript" src="{{ url_for('static', filename='js/functions.js') }}"></script>
</head>
<body>
<div class="container">
<div>
<p class="bk-plot-info">None-Bokeh doc element</p>
<form onsubmit="return false">
<label for="name">Update title of Bokeh plot</label>
<input type="text" id="name" name="title">
<input type="button" value="Update" onclick="updateBokehPlotTitle(this.form)">
</form>
</div>
<div>
<h3>Bokeh session</h3>
<p>Tap line glyph using TapTool to update non-Bokeh doc element</p>
<div>
{{ bk_script | safe }}
</div>
</div>
</div>
</body>
</html>
// functions.js
function bokeh_func() {
console.log('Hello from imported function called through Bokeh CustomJS');
}
function updateBokehPlotTitle(form) {
console.log('Update Bokeh title');
const bkplt = window.Bokeh.documents[0].get_model_by_name('plot');
bkplt.title.text = form.title.value;
}
/*styles.css*/
html {
font-family: sans-serif;
font-size: 62.5%;
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
}
body {
font-size: 1.6em;
line-height: 1.5;
font-weight: 300;
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, "Helvetica Neue", Oxygen, Cantarell, sans-serif;
color: #444444;
margin: 0px;
background-color: white;
padding-top: 0px;
}
.container {
max-width: 800px;
margin: auto;
}