使用 Multiselect 在 Bokeh 中启用线条
Enable lines in Bokeh using Multiselect
我在几个位置读取了带有时间和湿度数据的记录器。为了探索数据并分发它,我使用 Python(通过 jupyter notebook)和 Bokeh。
为了简化数据探索,我希望能够启用和禁用 I read out loggers with time and humidity data on a few locations。为了探索数据并分发它,我使用 Python(通过 jupyter notebook)和 Bokeh。
为了简化数据探索,我希望能够启用和禁用位置图(将来还有湿度 and/or 温度)。为此,我想使用 multiselect.
我根据 得到了 select 多行,但是当我尝试它时,它会选择第一个 n 个位置,不是我 selected.
进口
import numpy as np
import itertools
from collections import OrderedDict
from bokeh.io import push_notebook, show, output_notebook, output_file
from bokeh.layouts import row
from bokeh.palettes import Set1_6
from bokeh.plotting import figure as bf
from bokeh.models import MultiSelect, CustomJS, Range1d, LinearAxis, ColumnDataSource
from bokeh.resources import CDN
output_notebook()
辅助函数
一个函数生成示例数据
def generate_example_data(x, param=1):
t = 20 + param + np.sin(x * (1 + param))
rh = 50 + param + 10 * np.tan(x * (1 + param))
return {"x": x.copy(), "t": t, "rh": rh}
另一个函数生成 javascript 代码来使线条可见或不可见。我尝试了几种方法,但 none 给出了正确的结果。我还添加了日志记录,以便能够查看发生了什么以及触发了哪些 if-else 子句。
def generate_selector_code(locations):
for index, location in enumerate(locations):
res_str = """ if (%(index)i in multiselect.attributes.value) {
%(loc)s_t.visible = true;
%(loc)s_rh.visible = true;
console.log('enabling0 %(loc)s' );
} else {
%(loc)s_t.visible = false;
%(loc)s_rh.visible = false;
console.log('disabling0 %(loc)s' );
}
if ('%(index)i' in multiselect.attributes.value) {
%(loc)s_t.visible = true;
%(loc)s_rh.visible = true;
console.log('enabling1 %(loc)s' );
} else {
%(loc)s_t.visible = false;
%(loc)s_rh.visible = false;
console.log('disabling1 %(loc)s' );
}
if ('%(loc)s' in multiselect.attributes.value) {
%(loc)s_t.visible = true;
%(loc)s_rh.visible = true;
console.log('enabling2 %(loc)s' );
} else {
%(loc)s_t.visible = false;
%(loc)s_rh.visible = false;
console.log('disabling2 %(loc)s' );
}
"""%({"index": index, "loc": location})
# other method's I've tested but which result into an error which states that Object does not have an attribute includes
# if (multiselect.attributes.value.includes('%(index)i')) {
# %(loc)s_t.visible = true;
# %(loc)s_rh.visible = true;
# console.log('enabling3 %(loc)s' );
# } else {
# %(loc)s_t.visible = false;
# %(loc)s_rh.visible = false;
# console.log('disabling3 %(loc)s' );
# }
# if (multiselect.attributes.value.includes('%(loc)s')) {
# %(loc)s_t.visible = true;
# %(loc)s_rh.visible = true;
# console.log('enabling4 %(loc)s' );
# } else {
# %(loc)s_t.visible = false;
# %(loc)s_rh.visible = false;
# console.log('disabling4 %(loc)s' );
# }
yield res_str
设置情节
生成示例数据和select要使用的工具
locations = ["loc_one", "loc_two", "loc_three"]
x = np.linspace(0, 4 * np.pi, 20)
data_per_loc = OrderedDict()
for i, loc in enumerate(locations):
data_per_loc[loc] = generate_example_data(x, i)
tools="pan,box_zoom,reset,resize,save,crosshair,hover,xbox_zoom, wheel_zoom"
生成绘图
实际生成绘图的函数。它创建实际的散景图并连接 javascript 代码以启用或禁用不同的行
def generate_plot(data_per_loc):
palet = itertools.cycle(Set1_6)
p = bf(title="test", plot_height=500, plot_width=1000, tools=tools, y_range=(17, 27),
toolbar_location="above")
p.xaxis.axis_label = "x"
p.yaxis.axis_label = "Temperature [°C]"
p.extra_y_ranges = {"humidity": Range1d(start=30, end=80)}
p.add_layout(LinearAxis(y_range_name="humidity", axis_label="Relative Humidity [%Rh]"), 'right')
plot_locations = OrderedDict()
for location, datadict in data_per_loc.items():
colour = next(palet)
source = ColumnDataSource(datadict)
t = p.line(x='x', y='t', color=colour, source=source, legend=location)
rh = p.line(x='x', y='rh', source=source, color=colour,
legend=location, y_range_name='humidity',
line_dash="dashed", )
plot_locations.update({location+"_t": t, location+"_rh": rh})
code = "console.log('value: ' + multiselect.attributes.value);\n " + "console.log('value_type: ' + Object.prototype.toString.call(multiselect.attributes.value).slice(8, -1));\n " + "console.log('options: ' + multiselect.attributes.options);\n " + "".join(generate_selector_code(data_per_loc.keys()))
return p, code, plot_locations
生成的代码如下所示:
"console.log('value: ' + multiselect.attributes.value);
console.log('value_type: ' + Object.prototype.toString.call(multiselect.attributes.value).slice(8, -1));
console.log('options: ' + multiselect.attributes.options);
if (0 in multiselect.attributes.value) {
loc_one_t.visible = true;
loc_one_rh.visible = true;
console.log('enabling0 loc_one' );
} else {
loc_one_t.visible = false;
loc_one_rh.visible = false;
console.log('disabling0 loc_one' );
}
if ('0' in multiselect.attributes.value) {
loc_one_t.visible = true;
loc_one_rh.visible = true;
console.log('enabling1 loc_one' );
} else {
loc_one_t.visible = false;
loc_one_rh.visible = false;
console.log('disabling1 loc_one' );
}
if ('loc_one' in multiselect.attributes.value) {
loc_one_t.visible = true;
loc_one_rh.visible = true;
console.log('enabling2 loc_one' );
} else {
loc_one_t.visible = false;
loc_one_rh.visible = false;
console.log('disabling2 loc_one' );
}
if (1 in multiselect.attributes.value) {
loc_two_t.visible = true;
loc_two_rh.visible = true;
console.log('enabling0 loc_two' );
} else {
loc_two_t.visible = false;
loc_two_rh.visible = false;
console.log('disabling0 loc_two' );
}
if ('1' in multiselect.attributes.value) {
loc_two_t.visible = true;
loc_two_rh.visible = true;
console.log('enabling1 loc_two' );
} else {
loc_two_t.visible = false;
loc_two_rh.visible = false;
console.log('disabling1 loc_two' );
}
if ('loc_two' in multiselect.attributes.value) {
loc_two_t.visible = true;
loc_two_rh.visible = true;
console.log('enabling2 loc_two' );
} else {
loc_two_t.visible = false;
loc_two_rh.visible = false;
console.log('disabling2 loc_two' );
}
if (2 in multiselect.attributes.value) {
loc_three_t.visible = true;
loc_three_rh.visible = true;
console.log('enabling0 loc_three' );
} else {
loc_three_t.visible = false;
loc_three_rh.visible = false;
console.log('disabling0 loc_three' );
}
if ('2' in multiselect.attributes.value) {
loc_three_t.visible = true;
loc_three_rh.visible = true;
console.log('enabling1 loc_three' );
} else {
loc_three_t.visible = false;
loc_three_rh.visible = false;
console.log('disabling1 loc_three' );
}
if ('loc_three' in multiselect.attributes.value) {
loc_three_t.visible = true;
loc_three_rh.visible = true;
console.log('enabling2 loc_three' );
} else {
loc_three_t.visible = false;
loc_three_rh.visible = false;
console.log('disabling2 loc_three' );
}
"
第一次尝试将它们整合在一起
output_file("c:\html\multiselect_loc.html")
p, code, plot_locations = generate_plot(data_per_loc)
ms_options = locations
ms_value = locations
callback = CustomJS(code=code, args={})
multiselect = MultiSelect(title="Location:", options=ms_options, value=ms_value, callback=callback)
callback.args = dict(**plot_locations, multiselect=multiselect)
layout = row(p, multiselect)
show(layout)
第二次尝试将其组合在一起
我想也许 javascript 将字符串作为值有问题,所以我尝试使用整数作为 multiselect
的值
output_file("c:\html\multiselect_val.html")
p, code, plot_locations = generate_plot(data_per_loc)
ms_options = [(str(i), v) for i , v in enumerate(locations)]
ms_value = [str(i) for i in range(len(locations))]
callback = CustomJS(code=code, args={})
multiselect = MultiSelect(title="Location:", options=ms_options value=ms_value, callback=callback)
callback.args = dict(**plot_locations, multiselect=multiselect)
layout = row(p, multiselect)
show(layout)
结果
Bokeh 绘制线条没有问题,但 selection 表现得很奇怪。它没有显示我想要的位置,而是给出了第一个 n 行和前 2 个 selection 方法,而第 3 个没有任何内容。
javascript 控制台 returns 类似于:
value: loc_two
value_type: Array
options: loc_one,loc_two,loc_three
enabling0 loc_one
enabling1 loc_one
disabling2 loc_one
disabling0 loc_two
disabling1 loc_two
disabling2 loc_two
disabling0 loc_three
disabling1 loc_three
disabling2 loc_three
value: loc_two,loc_three
value_type: Array
options: loc_one,loc_two,loc_three
enabling0 loc_one
enabling1 loc_one
disabling2 loc_one
enabling0 loc_two
enabling1 loc_two
disabling2 loc_two
disabling0 loc_three
disabling1 loc_three
disabling2 loc_three
我已经在我的 github 存储库 my github repo 以及 javascript 控制台输出中添加了所有示例代码。所有这些都是用 IE11 测试的(公司限制)。
我找到了罪魁祸首,确实是检查数组的键(0 到 len-1)而不是值
res_str = """\
if (multiselect.attributes.value.indexOf('%(loc)s')>-1) {
%(loc)s_t.visible = true;
%(loc)s_rh.visible = true;
console.log('enabling5 %(loc)s' );
} else {
%(loc)s_t.visible = false;
%(loc)s_rh.visible = false;
console.log('disabling5 %(loc)s' );
}
"""%({"index": index, "loc": location})
是 def generate_selector_code(locations):
中的正确测试
我在查看 the bokeh example which uses CustomJS.from_coffeescript()
and browsing throught the CoffeeScript documentation
中的代码时注意到了这一点
of => in
in => no JS equivalent
另一种方法是使用 CustomJS.from_coffeescript()
并将代码从 JS 更改为 CoffeeScript
res_str = """\
if '%(loc)s' in multiselect.attributes.value
%(loc)s_t.visible = true
%(loc)s_rh.visible = true
console.log 'enabling %(loc)s'
} else {
%(loc)s_t.visible = false
%(loc)s_rh.visible = false
console.log 'disabling %(loc)s'
}
"""%({"index": index, "loc": location})
我在几个位置读取了带有时间和湿度数据的记录器。为了探索数据并分发它,我使用 Python(通过 jupyter notebook)和 Bokeh。
为了简化数据探索,我希望能够启用和禁用 I read out loggers with time and humidity data on a few locations。为了探索数据并分发它,我使用 Python(通过 jupyter notebook)和 Bokeh。
为了简化数据探索,我希望能够启用和禁用位置图(将来还有湿度 and/or 温度)。为此,我想使用 multiselect.
我根据
进口
import numpy as np
import itertools
from collections import OrderedDict
from bokeh.io import push_notebook, show, output_notebook, output_file
from bokeh.layouts import row
from bokeh.palettes import Set1_6
from bokeh.plotting import figure as bf
from bokeh.models import MultiSelect, CustomJS, Range1d, LinearAxis, ColumnDataSource
from bokeh.resources import CDN
output_notebook()
辅助函数
一个函数生成示例数据
def generate_example_data(x, param=1):
t = 20 + param + np.sin(x * (1 + param))
rh = 50 + param + 10 * np.tan(x * (1 + param))
return {"x": x.copy(), "t": t, "rh": rh}
另一个函数生成 javascript 代码来使线条可见或不可见。我尝试了几种方法,但 none 给出了正确的结果。我还添加了日志记录,以便能够查看发生了什么以及触发了哪些 if-else 子句。
def generate_selector_code(locations):
for index, location in enumerate(locations):
res_str = """ if (%(index)i in multiselect.attributes.value) {
%(loc)s_t.visible = true;
%(loc)s_rh.visible = true;
console.log('enabling0 %(loc)s' );
} else {
%(loc)s_t.visible = false;
%(loc)s_rh.visible = false;
console.log('disabling0 %(loc)s' );
}
if ('%(index)i' in multiselect.attributes.value) {
%(loc)s_t.visible = true;
%(loc)s_rh.visible = true;
console.log('enabling1 %(loc)s' );
} else {
%(loc)s_t.visible = false;
%(loc)s_rh.visible = false;
console.log('disabling1 %(loc)s' );
}
if ('%(loc)s' in multiselect.attributes.value) {
%(loc)s_t.visible = true;
%(loc)s_rh.visible = true;
console.log('enabling2 %(loc)s' );
} else {
%(loc)s_t.visible = false;
%(loc)s_rh.visible = false;
console.log('disabling2 %(loc)s' );
}
"""%({"index": index, "loc": location})
# other method's I've tested but which result into an error which states that Object does not have an attribute includes
# if (multiselect.attributes.value.includes('%(index)i')) {
# %(loc)s_t.visible = true;
# %(loc)s_rh.visible = true;
# console.log('enabling3 %(loc)s' );
# } else {
# %(loc)s_t.visible = false;
# %(loc)s_rh.visible = false;
# console.log('disabling3 %(loc)s' );
# }
# if (multiselect.attributes.value.includes('%(loc)s')) {
# %(loc)s_t.visible = true;
# %(loc)s_rh.visible = true;
# console.log('enabling4 %(loc)s' );
# } else {
# %(loc)s_t.visible = false;
# %(loc)s_rh.visible = false;
# console.log('disabling4 %(loc)s' );
# }
yield res_str
设置情节
生成示例数据和select要使用的工具
locations = ["loc_one", "loc_two", "loc_three"]
x = np.linspace(0, 4 * np.pi, 20)
data_per_loc = OrderedDict()
for i, loc in enumerate(locations):
data_per_loc[loc] = generate_example_data(x, i)
tools="pan,box_zoom,reset,resize,save,crosshair,hover,xbox_zoom, wheel_zoom"
生成绘图
实际生成绘图的函数。它创建实际的散景图并连接 javascript 代码以启用或禁用不同的行
def generate_plot(data_per_loc):
palet = itertools.cycle(Set1_6)
p = bf(title="test", plot_height=500, plot_width=1000, tools=tools, y_range=(17, 27),
toolbar_location="above")
p.xaxis.axis_label = "x"
p.yaxis.axis_label = "Temperature [°C]"
p.extra_y_ranges = {"humidity": Range1d(start=30, end=80)}
p.add_layout(LinearAxis(y_range_name="humidity", axis_label="Relative Humidity [%Rh]"), 'right')
plot_locations = OrderedDict()
for location, datadict in data_per_loc.items():
colour = next(palet)
source = ColumnDataSource(datadict)
t = p.line(x='x', y='t', color=colour, source=source, legend=location)
rh = p.line(x='x', y='rh', source=source, color=colour,
legend=location, y_range_name='humidity',
line_dash="dashed", )
plot_locations.update({location+"_t": t, location+"_rh": rh})
code = "console.log('value: ' + multiselect.attributes.value);\n " + "console.log('value_type: ' + Object.prototype.toString.call(multiselect.attributes.value).slice(8, -1));\n " + "console.log('options: ' + multiselect.attributes.options);\n " + "".join(generate_selector_code(data_per_loc.keys()))
return p, code, plot_locations
生成的代码如下所示:
"console.log('value: ' + multiselect.attributes.value);
console.log('value_type: ' + Object.prototype.toString.call(multiselect.attributes.value).slice(8, -1));
console.log('options: ' + multiselect.attributes.options);
if (0 in multiselect.attributes.value) {
loc_one_t.visible = true;
loc_one_rh.visible = true;
console.log('enabling0 loc_one' );
} else {
loc_one_t.visible = false;
loc_one_rh.visible = false;
console.log('disabling0 loc_one' );
}
if ('0' in multiselect.attributes.value) {
loc_one_t.visible = true;
loc_one_rh.visible = true;
console.log('enabling1 loc_one' );
} else {
loc_one_t.visible = false;
loc_one_rh.visible = false;
console.log('disabling1 loc_one' );
}
if ('loc_one' in multiselect.attributes.value) {
loc_one_t.visible = true;
loc_one_rh.visible = true;
console.log('enabling2 loc_one' );
} else {
loc_one_t.visible = false;
loc_one_rh.visible = false;
console.log('disabling2 loc_one' );
}
if (1 in multiselect.attributes.value) {
loc_two_t.visible = true;
loc_two_rh.visible = true;
console.log('enabling0 loc_two' );
} else {
loc_two_t.visible = false;
loc_two_rh.visible = false;
console.log('disabling0 loc_two' );
}
if ('1' in multiselect.attributes.value) {
loc_two_t.visible = true;
loc_two_rh.visible = true;
console.log('enabling1 loc_two' );
} else {
loc_two_t.visible = false;
loc_two_rh.visible = false;
console.log('disabling1 loc_two' );
}
if ('loc_two' in multiselect.attributes.value) {
loc_two_t.visible = true;
loc_two_rh.visible = true;
console.log('enabling2 loc_two' );
} else {
loc_two_t.visible = false;
loc_two_rh.visible = false;
console.log('disabling2 loc_two' );
}
if (2 in multiselect.attributes.value) {
loc_three_t.visible = true;
loc_three_rh.visible = true;
console.log('enabling0 loc_three' );
} else {
loc_three_t.visible = false;
loc_three_rh.visible = false;
console.log('disabling0 loc_three' );
}
if ('2' in multiselect.attributes.value) {
loc_three_t.visible = true;
loc_three_rh.visible = true;
console.log('enabling1 loc_three' );
} else {
loc_three_t.visible = false;
loc_three_rh.visible = false;
console.log('disabling1 loc_three' );
}
if ('loc_three' in multiselect.attributes.value) {
loc_three_t.visible = true;
loc_three_rh.visible = true;
console.log('enabling2 loc_three' );
} else {
loc_three_t.visible = false;
loc_three_rh.visible = false;
console.log('disabling2 loc_three' );
}
"
第一次尝试将它们整合在一起
output_file("c:\html\multiselect_loc.html")
p, code, plot_locations = generate_plot(data_per_loc)
ms_options = locations
ms_value = locations
callback = CustomJS(code=code, args={})
multiselect = MultiSelect(title="Location:", options=ms_options, value=ms_value, callback=callback)
callback.args = dict(**plot_locations, multiselect=multiselect)
layout = row(p, multiselect)
show(layout)
第二次尝试将其组合在一起
我想也许 javascript 将字符串作为值有问题,所以我尝试使用整数作为 multiselect
的值output_file("c:\html\multiselect_val.html")
p, code, plot_locations = generate_plot(data_per_loc)
ms_options = [(str(i), v) for i , v in enumerate(locations)]
ms_value = [str(i) for i in range(len(locations))]
callback = CustomJS(code=code, args={})
multiselect = MultiSelect(title="Location:", options=ms_options value=ms_value, callback=callback)
callback.args = dict(**plot_locations, multiselect=multiselect)
layout = row(p, multiselect)
show(layout)
结果
Bokeh 绘制线条没有问题,但 selection 表现得很奇怪。它没有显示我想要的位置,而是给出了第一个 n 行和前 2 个 selection 方法,而第 3 个没有任何内容。
javascript 控制台 returns 类似于:
value: loc_two
value_type: Array
options: loc_one,loc_two,loc_three
enabling0 loc_one
enabling1 loc_one
disabling2 loc_one
disabling0 loc_two
disabling1 loc_two
disabling2 loc_two
disabling0 loc_three
disabling1 loc_three
disabling2 loc_three
value: loc_two,loc_three
value_type: Array
options: loc_one,loc_two,loc_three
enabling0 loc_one
enabling1 loc_one
disabling2 loc_one
enabling0 loc_two
enabling1 loc_two
disabling2 loc_two
disabling0 loc_three
disabling1 loc_three
disabling2 loc_three
我已经在我的 github 存储库 my github repo 以及 javascript 控制台输出中添加了所有示例代码。所有这些都是用 IE11 测试的(公司限制)。
我找到了罪魁祸首,确实是检查数组的键(0 到 len-1)而不是值
res_str = """\
if (multiselect.attributes.value.indexOf('%(loc)s')>-1) {
%(loc)s_t.visible = true;
%(loc)s_rh.visible = true;
console.log('enabling5 %(loc)s' );
} else {
%(loc)s_t.visible = false;
%(loc)s_rh.visible = false;
console.log('disabling5 %(loc)s' );
}
"""%({"index": index, "loc": location})
是 def generate_selector_code(locations):
我在查看 the bokeh example which uses CustomJS.from_coffeescript()
and browsing throught the CoffeeScript documentation
of => in
in => no JS equivalent
另一种方法是使用 CustomJS.from_coffeescript()
并将代码从 JS 更改为 CoffeeScript
res_str = """\
if '%(loc)s' in multiselect.attributes.value
%(loc)s_t.visible = true
%(loc)s_rh.visible = true
console.log 'enabling %(loc)s'
} else {
%(loc)s_t.visible = false
%(loc)s_rh.visible = false
console.log 'disabling %(loc)s'
}
"""%({"index": index, "loc": location})